ilokesto

React

Use the React adapter when a React component needs a subscribed value from an @ilokesto/store-backed state container. Import from @ilokesto/state/react, not from the root package.

Adapter types

UseState and UseReducer describe the hooks returned by the plain-state and reducer create() overloads. Import them from the same React subpath when a public API accepts either hook type.

import type { UseReducer, UseState } from '@ilokesto/state/react';

Plain state hook

Plain state returns the same shape as a small React state hook: [selection, setState].

import { create } from '@ilokesto/state/react';

type CounterState = { count: number; label: string };

const useCounter = create<CounterState>({ count: 0, label: 'Clicks' });

export function CounterButton() {
  const [count, setCounter] = useCounter((state) => state.count);
  const [label] = useCounter((state) => state.label);

  return (
    <button onClick={() => setCounter((prev) => ({ ...prev, count: prev.count + 1 }))}>
      {label}: {count}
    </button>
  );
}

The selector decides the value returned as the first tuple item. The writer still updates the full state, so keep object updates immutable.

Reducer hook

Reducer state uses the same create(reduce, initialState) overload. The tuple writer becomes dispatch.

import { create } from '@ilokesto/state/react';

type Action = { type: 'increment' } | { type: 'reset' };

const reduce = (state: { count: number }, action: Action) => {
  if (action.type === 'increment') return { count: state.count + 1 };
  if (action.type === 'reset') return { count: 0 };
  return state;
};

const useCounter = create(reduce, { count: 0 });

function ReducerCounter() {
  const [count, dispatch] = useCounter((state) => state.count);
  return <button onClick={() => dispatch({ type: 'increment' })}>{count}</button>;
}

Outside React lifecycle

readOnly() and writeOnly() are attached to the hook function. Use them in modules, service callbacks, tests, or event buses that should not call a hook.

const readCount = useCounter.readOnly((state) => state.count);
const dispatch = useCounter.writeOnly();
dispatch({ type: 'reset' });

Caveats

  • The returned function is a React hook because it uses useSyncExternalStore; call it only inside components or custom hooks.
  • getServerSnapshot starts from the store initial state, so SSR hydration should use the same initial state on server and client.
  • Selectors should be pure and cheap. React memoizes snapshots, but expensive selectors still run during render/snapshot work.

On this page