Skip to content
react-global-state-hooks

Selectors and derived state

Subscribe to a slice, derive values, depend on local state, share selector hooks, and control equality.

GuideTypeScriptTry an example
useStore
useUsers
useActiveUsers

Compose a selection. Reuse the hook. Subscribe to its result.

The problem this solves

A component that reads the whole state renders on every change, even when it shows one field. A selector narrows what the component depends on.

Select a slice

tsx
const [name] = useProfile((profile) => profile.name);

After every store change the selector runs and the result is compared with the previous result using strict equality (===). The component renders only when they differ. useProfile.select(selector) is the same without the setter.

Selections that depend on local state

A selector often depends on something that is not in the store, such as a filter chosen in the component. List those values in dependencies. When one changes, the selector runs again on the next render.

TodoList.tsx
import { useState } from 'react';
import { createGlobalState, shallowCompare } from 'react-global-state-hooks';

export const useTodos = createGlobalState({
  todos: [
    { id: 1, text: 'Write docs', done: true },
    { id: 2, text: 'Ship examples', done: false },
    { id: 3, text: 'Review', done: false },
  ],
});

export function TodoList() {
  // Local state that is NOT in the store, but the selection depends on it.
  const [showDone, setShowDone] = useState(false);

  const [visible] = useTodos((state) => state.todos.filter((todo) => todo.done === showDone), {
    // re-run the selector when this changes, not only when the store does
    dependencies: [showDone],
    isEqual: shallowCompare,
  });

  return (
    <div>
      <button onClick={() => setShowDone(!showDone)}>{showDone ? 'Show open' : 'Show done'}</button>
      <ul>
        {visible.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

dependencies can also be passed as the second argument directly: useTodos(selector, [showDone]).

Equality options

OptionDefaultUse it to
isEqual(current, next)=== on the selected valueTreat a rebuilt array or object as unchanged. shallowCompare is exported for this.
isEqualRoot(current, next)=== on the whole stateSkip running the selector at all for certain root changes.

Share a derivation with selector hooks

store.createSelectorHook(selector) builds a hook once. It computes the derived value once per store change, not once per component, and it chains. It returns the selected value directly, not a tuple.

selector-hooks.tsx
import { createGlobalState, shallowCompare } from 'react-global-state-hooks';

export const useStore = createGlobalState({
  users: [
    { id: 1, name: 'Ada', active: true },
    { id: 2, name: 'Grace', active: false },
  ],
  currentUserId: 1,
});

// A selector hook is created once and shared. It returns the selected value, not a tuple.
export const useUsers = useStore.createSelectorHook((state) => state.users);

// Selector hooks chain. `isEqual` stops the derived list from changing when its items did not.
export const useActiveUsers = useUsers.createSelectorHook((users) => users.filter((user) => user.active), {
  isEqual: shallowCompare,
});

export function ActiveUsers() {
  const active = useActiveUsers();

  return <p>{active.map((user) => user.name).join(', ')}</p>;
}

Selector hooks are read-only: they have no setter. getState() and subscribe() work on them too.

Observe outside React

createObservable gives you the same slice as a subscribable object that needs no component. It calls back immediately with the current value, then on each change.

observable.ts
import { createGlobalState } from 'react-global-state-hooks';

export const useCounter = createGlobalState({ count: 0, label: 'clicks' });

// An observable is a read-only, subscribable slice of the store. It needs no component.
export const count$ = useCounter.createObservable((state) => state.count);

export const seen: number[] = [];

// Runs immediately with the current value, then whenever `count` changes.
export const stop = count$.subscribe((count) => seen.push(count));

Call dispose() on an observable or selector hook you no longer need, so it stops listening to its parent.

Documentation
↑↓ Navigate ↵ Open Esc CloseRGSH docs
Get started

Shared state. Precise subscriptions.
Built-in DevTools.