DEV Community

Ng Dream
Ng Dream

Posted on

Pulsy Readme updated

Pulsy - A Lightweight State Management Library for React

Pulsy is a lightweight, flexible, and easy-to-use state management library for React that provides features such as persistence, middleware, memoization, computed and composed stores, time travel, and DevTools integration. It helps you efficiently manage global state in React applications without unnecessary complexity.

Features

  • Global State Management: Manage state across components using a simple store API.
  • Persistence: Automatically persist store data in localStorage or custom storage solutions.
  • Middleware: Modify and handle store updates through middleware functions.
  • Memoization: Avoid unnecessary renders by using memoized state values.
  • Computed Stores: Derive and calculate state from existing stores.
  • Composable Stores: Combine multiple stores into one store for modular state management.
  • Time-Travel: Rewind and forward state changes.
  • DevTools Integration: Track and debug state updates in development mode.

Installation



npm install pulsy
# or
yarn add pulsy


Enter fullscreen mode Exit fullscreen mode

Basic Usage

Step 1: Configure Pulsy

Pulsy can be configured globally to enable DevTools, default memoization, persistence, and callback hooks for store creation and updates.



import { configurePulsy } from 'pulsy';

configurePulsy({
  enableDevTools: process.env.NODE_ENV === 'development',
  persist: true, // Globally enable persistence by default
  defaultMemoize: true, // Enable memoization for all stores by default
  onStoreCreate: (name, initialValue) => console.log(`Store "${name}" created! Initial value:`, initialValue),
  onStoreUpdate: (name, newValue) => console.log(`Store "${name}" updated! New value:`, newValue),
});


Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Store

To create a store, use the createStore function. A store holds the global state and can be used anywhere in your React application.



import { createStore } from 'pulsy';

// Create a store named 'counter' with an initial value of 0
createStore('counter', 0);


Enter fullscreen mode Exit fullscreen mode

Step 3: Use the Store in a Component

Pulsy provides the usePulsy hook to access and update the store’s value in your React components. Let’s create a counter component:



import usePulsy from 'pulsy';

function CounterComponent() {
  const [count, setCount] = usePulsy<number>('counter');

  const increment = () => setCount((prev) => prev + 1);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default CounterComponent;


Enter fullscreen mode Exit fullscreen mode

Persistence

Pulsy makes it easy to persist store values in localStorage or any other custom storage system. Simply pass the persist option when creating the store.



createStore('counter', 0, { persist: true });


Enter fullscreen mode Exit fullscreen mode

The value of the counter store will now persist across page reloads.

Example: Using Custom Storage

You can also configure Pulsy to use custom storage, such as sessionStorage or any other storage engine that implements the Storage interface:



createStore('sessionCounter', 0, {
  persist: {
    storage: sessionStorage, // Use sessionStorage instead of localStorage
    serialize: (value) => JSON.stringify(value),
    deserialize: (value) => JSON.parse(value),
  },
});


Enter fullscreen mode Exit fullscreen mode

This will store sessionCounter in sessionStorage.


Middleware

Middleware allows you to intercept and modify store updates before they are committed. You can add middleware when creating a store, or later using addMiddleware.



const loggingMiddleware = (nextValue, prevValue, storeName) => {
  console.log(`[${storeName}] changed from ${prevValue} to ${nextValue}`);
  return nextValue;
};

createStore('counter', 0, { middleware: [loggingMiddleware] });


Enter fullscreen mode Exit fullscreen mode

In this example, the middleware logs each state change in the counter store.

Example: Asynchronous Middleware

Pulsy supports asynchronous middleware for handling asynchronous tasks like API calls:



const asyncMiddleware = async (nextValue, prevValue, storeName) => {
  console.log(`Fetching data before updating ${storeName}...`);
  const data = await fetch('https://api.example.com/data').then((res) => res.json());
  return nextValue + data.amount;
};

createStore('counter', 0, { middleware: [asyncMiddleware] });


Enter fullscreen mode Exit fullscreen mode

In this example, the middleware fetches some data from an API before updating the store.


Time-Travel State Management

Pulsy allows you to manage state history using the useTimeTravel hook, giving you the ability to undo and redo state changes.



import { useTimeTravel } from 'pulsy';

function TimeTravelCounter() {
  const [count, setCount, undo, redo] = useTimeTravel<number>('counter');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={undo}>Undo</button>
      <button onClick={redo}>Redo</button>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

Example: Display State History

You can access the full history of state changes using the historyRef provided by useTimeTravel:



function HistoryCounter() {
  const [count, setCount, undo, redo, history] = useTimeTravel<number>('counter');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={undo}>Undo</button>
      <button onClick={redo}>Redo</button>
      <p>History: {history.join(', ')}</p>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

Computed Stores

Computed stores derive their state from other stores. Pulsy allows you to create stores whose values are based on one or more other stores.



import { createComputedStore } from 'pulsy';

createComputedStore('doubleCounter', () => {
  const counter = getStoreValue<number>('counter');
  return counter * 2;
}, ['counter']);


Enter fullscreen mode Exit fullscreen mode

Here, doubleCounter is automatically updated whenever the counter store changes.

Example: Display Computed Store in a Component

You can now access the computed store just like a regular store:



function DoubleCounterComponent() {
  const [doubleCount] = usePulsy<number>('doubleCounter');

  return (
    <div>
      <p>Double Counter: {doubleCount}</p>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

Composing Stores

Pulsy supports composing multiple stores into a single store. This is especially useful for managing complex state by grouping related pieces of state together.



import { composeStores } from 'pulsy';

const [getComposedStore, setComposedStore] = composeStores('userProfile', {
  username: 'userNameStore',
  age: 'ageStore',
});

const UserProfileComponent = () => {
  const userProfile = getComposedStore();

  return (
    <div>
      <p>Username: {userProfile.username}</p>
      <p>Age: {userProfile.age}</p>
    </div>
  );
};


Enter fullscreen mode Exit fullscreen mode

Example: Updating Composed Stores

You can also update specific parts of a composed store using the setComposedStore function:



setComposedStore({
  username: 'newUsername',
});


Enter fullscreen mode Exit fullscreen mode

Namespaced Stores

Pulsy allows you to create namespaced stores to keep related stores organized and avoid naming collisions in large applications.



import { createNamespacedStore } from 'pulsy';

// Create a namespaced store for user-related data
const useUserStore = createNamespacedStore('user');

function UserComponent() {
  const [username, setUsername] = useUserStore<string>('username');

  return (
    <div>
      <p>Username: {username}</p>
      <button onClick={() => setUsername('newUsername')}>Update Username</button>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

Here, all user-related stores (e.g., user:username, user:age) are grouped under the user namespace.


DevTools Integration

Pulsy integrates with browser DevTools to help track and debug store updates. When DevTools are enabled, you’ll see logs about store updates, state changes, and performance measurements in your console.



configurePulsy({
  enableDevTools: true, // Logs detailed store activity to the console
});


Enter fullscreen mode Exit fullscreen mode

Pulsy logs helpful information such as when a store is created or updated, middleware execution, and time-travel actions in development mode.


Full Example: Managing User Profile with Persistence and Middleware

Let’s combine multiple Pulsy features into a single example.



import { createStore, usePulsy, configurePulsy } from 'pulsy';

// Global configuration
configurePulsy({
  enableDevTools: true,
  persist: true,
});

// Middleware to log store updates
const loggingMiddleware = (nextValue, prevValue, storeName) => {
  console.log(`Store ${storeName}

 updated from ${prevValue} to ${nextValue}`);
  return nextValue;
};

// Create a store for user profile
createStore('userProfile', {
  username: 'guest',
  age: 25,
}, { middleware: [loggingMiddleware], persist: true });

// Component to manage user profile
function UserProfileComponent() {
  const [userProfile, setUserProfile] = usePulsy('userProfile');

  const updateUsername = () => {
    setUserProfile((prevProfile) => ({
      ...prevProfile,
      username: 'newUsername',
    }));
  };

  return (
    <div>
      <p>Username: {userProfile.username}</p>
      <p>Age: {userProfile.age}</p>
      <button onClick={updateUsername}>Change Username</button>
    </div>
  );
}

export default UserProfileComponent;


Enter fullscreen mode Exit fullscreen mode

In this example, the userProfile store is persisted, logged by middleware, and accessible via the usePulsy hook. The UserProfileComponent displays and updates the store in a simple UI.


Conclusion

Pulsy is a powerful and flexible state management library for React that provides out-of-the-box support for persistence, middleware, computed stores, time-travel, and DevTools. Its simple API and wide range of features make it suitable for both small and large-scale applications.

Top comments (0)