Getting started
Install react-global-state-hooks, create a store, read and update it, select slices, add actions, and use state outside React.
Install
npm install react-global-state-hooksThe package needs React 18 or newer and ships its own TypeScript types. It has no provider to mount and no build plugin to configure.
Create a store
A store is created with one call. The value you get back is a React hook, and the same function also
carries the store’s API (getState, setState, subscribe and more).
import { createGlobalState } from 'react-global-state-hooks';
// One call creates a store and returns its hook.
export const useCounter = createGlobalState({
count: 0,
step: 1,
});The store lives at module scope, outside the React tree. Any component that imports the hook shares it.
Read and update state
Inside a component the hook looks like useState: it returns the value and a setter. The setter takes a new
value or an updater function.
import { useCounter } from './Counter';
export function CounterButton() {
// Same shape as useState: [value, setValue].
const [counter, setCounter] = useCounter();
return (
<button
onClick={() =>
setCounter((current) => ({
...current,
count: current.count + current.step,
}))
}
>
Count: {counter.count}
</button>
);
}Select a slice
Passing a selector subscribes the component to just that part of the state. The selector runs when the store changes, and the component re-renders only if the result is different.
import { useCounter } from './Counter';
export function Labels() {
/**
* Re-renders only when `count` changes, not when `step` does.
* `select` returns just the selected value when you do not need the setter.
*/
const [count] = useCounter((state) => state.count);
const step = useCounter.select((state) => state.step);
return (
<>
<span>{count}</span>
<span>{step}</span>
</>
);
}A component that reads only count is not re-rendered when step changes. The
homepage demo shows this with live render counters.
Selectors that build new values
The comparison between the previous and the next selected value is strict equality (===). A selector such
as filter or map returns a new array every time it runs, so by default it looks like a change on every
store update. Pass isEqual to compare the result by content:
import { createGlobalState, shallowCompare } from 'react-global-state-hooks';
export const useTodos = createGlobalState({
filter: 'all',
todos: [
{ id: 1, text: 'Write docs', done: true },
{ id: 2, text: 'Ship examples', done: false },
],
});
export function OpenTodos() {
// `filter` returns a new array on every run. Strict equality would treat each result as a change,
// so compare the array's items instead.
const [open] = useTodos((state) => state.todos.filter((todo) => !todo.done), { isEqual: shallowCompare });
return <p>{open.length} open</p>;
}shallowCompare is exported by the package. You can also pass your own (current, next) => boolean.
Add actions
Actions group your updates behind names. An action is a function that returns a function; the inner
function receives the store tools such as setState and getState.
import { createGlobalState } from 'react-global-state-hooks';
export const useCounter = createGlobalState(
{ count: 0 },
{
actions: {
increment(by = 1) {
return ({ setState }) => {
setState((state) => ({ ...state, count: state.count + by }));
};
},
reset() {
return ({ setState }) => {
setState({ count: 0 });
};
},
},
},
);When a store has actions, the second item returned by the hook is the actions object instead of a setter.
import { useCounter } from './actions';
export function Counter() {
// With actions configured, the second tuple item is the actions object instead of a setter.
const [count, actions] = useCounter((state) => state.count);
return (
<div>
<output>{count}</output>
<button onClick={() => actions.increment()}>+1</button>
<button onClick={() => actions.increment(10)}>+10</button>
<button onClick={() => actions.reset()}>Reset</button>
</div>
);
}setState is still available on the hook (useCounter.setState) when you need to update outside the actions.
Use state outside React
The hook function also works as a plain store object. This is useful in API clients, event handlers and anything else that is not a component.
import { createGlobalState } from 'react-global-state-hooks';
export const useSession = createGlobalState({ token: null as string | null });
export function authHeader(): Record<string, string> {
// Read the current value from any module, no hook required.
const { token } = useSession.getState();
return token ? { Authorization: `Bearer ${token}` } : {};
}
export function signIn(token: string) {
useSession.setState({ token });
}
// Subscribe to a slice. The callback runs immediately with the current value, then on every change.
export const unsubscribe = useSession.subscribe(
(state) => state.token,
(token) => {
console.log('token changed:', token);
},
);subscribe calls your callback immediately with the current value. Pass { skipFirst: true } as the last
argument to wait for the first change instead.
Persist to localStorage
The web package can save a store to localStorage by adding a key.
import { createGlobalState } from 'react-global-state-hooks';
export const usePreferences = createGlobalState(
{ theme: 'light' as 'light' | 'dark', compact: false },
{
// Saved to window.localStorage on change, restored when the store is created.
localStorage: { key: 'preferences' },
},
);Persistence is skipped when localStorage is not available, for example while rendering on the server.
For server-rendered apps, follow the hydration guidance to keep the first
browser render consistent with the server HTML.
Where to go next
- Watch the video tutorial or try the live demo.
- Continue with Stores and hooks and Selectors and derived state, or browse the examples.