little-state-machine

repository·master·Indexed 23 days ago

https://github.com/beekai-oss/little-state-machine

A tiny, zero-dependency state management library for React 18+ designed for simplicity and performance. It features a global store initialized via createStore, built-in persistence (localStorage or sessionStorage), and optimized re-rendering using selectors via the useStateMachine hook. The library supports TypeScript type safety through the GlobalState interface and allows state interception via custom middleware.

Tokens
2.7K
Snippets
8
Records
15
Agent score
80%

What's inside little-state-machine

  1. Migrate to V5

    master

    If upgrading from a previous version to V5, note the following breaking changes:

    1. Removal of StateMachineProvider: The API is now simpler and does not require wrapping your app in a provider.
    2. Actions API Change: Actions must now be passed as an object payload: useStateMachine({ actions: { updateName } }).
    3. React Requirement: Ensure you are using React version 18 or higher.
  2. Configure TypeScript type safety for GlobalState

    master

    To enable full type safety for your global state, declare the GlobalState interface within a global.d.ts file. This allows useStateMachine to automatically infer the types of state and actions.

    import 'little-state-machine';
    
    declare module 'little-state-machine' {
      interface GlobalState {
        yourDetail: {
          name: string;
        };
      }
    }
  3. Understand the ActionsOutput return type

    master

    The ActionsOutput type defines the shape of the functions returned by the state machine to trigger actions. Instead of returning the state directly, these functions accept an optional payload (matching the original callback's second argument) and an optional options object.

    Passing { skipRender: true } in the options allows you to trigger an action without triggering a UI re-render.

    export type ActionsOutput<
      TCallback extends AnyCallback,
      TActions extends AnyActions<TCallback>,
    > = {
      [K in keyof TActions]: (
        payload?: Parameters<TActions[K]>[1],
        options?: { skipRender: boolean },
      ) => void;
    };
  4. How actions and middleware work in little-state-machine

    master

    When an action is called via the actions object returned by useStateMachine, the following lifecycle occurs:

    1. Execution: The provided callback is executed with the current state and the payload.
    2. Middleware: If middleWares are configured in createStore, they are executed in sequence. Each middleware receives the currentValue (the state resulting from the previous middleware or the action), the callback.name, and the payload. A middleware can return a new state or return undefined to keep the current state.
    3. State Update: The storeFactory.state is updated with the final result.
    4. Rendering: If options.skipRender was not set to true in the action call, React is notified to re-render the component.
    5. Persistence: If options.persist is set to PERSIST_OPTION.ACTION, the store is automatically saved to storage.
  5. Full usage example of little-state-machine

    master

    This example demonstrates initializing a store, defining an update action, using a selector for optimized rendering, and consuming state in components.

    import { createStore, useStateMachine } from 'little-state-machine';
    
    createStore({
      yourDetail: { name: '' },
    });
    
    function updateName(state, payload) {
      return {
        ...state,
        yourDetail: {
          ...state.yourDetail,
          ...payload,
        },
      };
    }
    
    function selector(state) {
      return state.yourDetails.name.length > 10;
    }
    
    function YourComponent() {
      const { actions, state } = useStateMachine({ actions: { updateName } });
    
      return (
        <buttton onClick={() => actions.updateName({ name: 'bill' })}>
          {state.yourDetail.name}
        </buttton>
      );
    }
    
    function YourComponentSelectorRender() {
      const { state } = useStateMachine({ selector });
      return <p>{state.yourDetail.name]</p>;
    }
    
    const App = () => (
      <>
        <YourComponent />
        <YourComponentSelectorRender />
      </>
    );
  6. Use the `useStateMachine` hook

    master

    The useStateMachine hook provides access to the global state and the actions defined during store creation. It supports TypeScript generics for type safety.

    Options:

    • actions (Record<string, Function>, optional): An object containing functions used to update the global state.
    • selector (Function, optional): A function used to isolate re-renders. The component will only re-render when the value returned by the selector changes.

    Returns:

    • actions: The object of actions provided to the hook.
    • state: The current global state.
    • getState: A function to retrieve the current state directly.
    const { actions, state, getState } = useStateMachine<T>({
      actions?: Record<string, Function> // Optional action to update global state
      selector?: Function, // Optional selector to isolate re-render based on selected state
    });
  7. Initialize the global store with `createStore`

    master

    Use createStore to initialize your application's global state. It accepts an initial state object and an optional configuration object.

    Configuration Options:

    • name (string, optional): Rename the store.
    • middleWares (array of functions, optional): Functions to invoke with each action.
    • storageType (Storage, optional): Specifies the storage mechanism. Defaults to sessionStorage. Can be sessionStorage or localStorage.
    • persist ('action' | 'beforeUnload' | 'none', optional):
      • 'none': State is not persisted.
      • 'action': State is saved to storage after a store action is completed (default).
      • 'beforeUnload': State is saved to storage before the page unloads.
    createStore(
      {
        yourDetail: { firstName: '', lastName: '' } // it's an object of your state
      },
      {
         name?: string; // rename the store
         middleWares?: [ log ]; // function to invoke each action
         storageType?: Storage; // session/local storage (default to session)
         persist?: 'action' // onAction is default if not provided
         // when 'none' is used then state is not persisted
         // when 'action' is used then state is saved to the storage after store action is completed
         // when 'beforeUnload' is used then state is saved to storage before page unloa
      },
    );
  8. Define a custom StateMachineOptions configuration

    master

    When initializing a state machine, you can provide a StateMachineOptions object to configure its behavior. Key options include:

    • name: A string identifier for the state machine.
    • middleWares: An array of MiddleWare functions that intercept state changes.
    • storageType: The storage mechanism used.
    • persist: Configuration for state persistence (using PERSIST_OPTION).
  9. Initialize a state machine with createStore()

    master

    Use createStore to initialize the global state and configure the machine's options. It accepts a defaultState of type GlobalState and an optional StateMachineOptions object.

    In non-production environments, createStore attaches debugging helpers to the window object:

    • window.__LSM_NAME__: The name of the store.
    • window.__LSM_RESET__: A function to clear the store from storage (if a storageType is configured).

    Note that createStore configures the underlying storeFactory singleton, which manages the state across your application.

  10. Use the useStateMachine hook in React components

    master

    The useStateMachine hook is the primary way to consume the state machine within React components. It provides access to actions, the current state, and a synchronous way to retrieve the state.

    Parameters

    • actions: An object containing callback functions that define how the state should be updated. These are wrapped to automatically trigger re-renders and middleware.
    • selector: An optional function (payload: TStore) => TStore used to select a specific slice of the state. The hook uses JSON.stringify comparison to determine if the selected slice has changed, triggering a re-render only when necessary.

    Returns

    An object containing:

    • actions: The processed action callbacks.
    • state: The current global state (triggers re-renders).
    • getState: A function to retrieve the current state without subscribing to updates.