Skip to main content
Create a performant distributed context state for React and compose reusable state logic.
const valueAtom = createAtom("Initial Value");
const todosAtom = createAtom([]);
const useAddTodo = createSynergy(valueAtom, todosAtom)
.createAction(() => (value, todos) => {
value.current = "";
An application state that follows the hierarchical structure of your components.
Authentication SynergyListSynergyTodoSynergyInputSynergyTodo AppMyTodo ListsTodosTodo Input FieldvalueAtom, isSelectedAtom, isFocusedAtomitemsAtom, selectedItemAtom
So how does it work?
Create hooks for reading and writing synergies of atoms.
const useTickTodo = createSynergy(todosAtom, tickedTodosAtom)
.createAction(index => (todos, tickedTodos) => {
const useTickedTodos = createSynergy(todosAtom, tickedTodosAtom)
(todos, tickedTodos) =>
todos.filter(todo => tickedTodos.includes(todo))
const useAllTodos = todosAtom.useValue;
Nest state providers arbitrarily. Synergies can access all atoms upwards the component hierarchy.
<SynergyProvider atoms={[todosAtom]}>
<SynergyProvider atoms={[tickedTodosAtom, starredTodosAtom]}>
<TodoList />
<SynergyProvider atoms={[valueAtom]}>
{/* Can access both valueAtom and todosAtom */}
<TodoInput />
Use Global State. Reuse Local State.
<SynergyProvider atoms={[authAtom, userDataAtom]}>
<SynergyProvider atoms={[selectedItemAtom, isExpandedAtom]}>
<Menu name="File" />
<SynergyProvider atoms={[selectedItemAtom, isExpandedAtom]}>
<Menu name="Edit" />

You can synergyze atoms even if they are supplied by different providers, as long as they are in one path in the component hierarchy. Define localized state in a reusable component and have it interact with more globalized state further up in the hierarchy.

Avoid unnecessary rerenders.
When accessing atom state, you do so by defining synergies of atoms and then specify selectors or actions on them. If you use a selector in a component, the component will rerender once one of the atoms updated from which it reads. However, when you define an action on a synergy of atoms, some of which are read from and only some are written to, dispatching the action will only update the atoms that actually are written to.
const useDropItem =
createSynergy(selectedItemAtom, targetAtom)
.createAction(() => async (selectedItem, target) => {
// target atom was updated and will rerender
// selectedItemAtom was only read from and will not
target.current = selectedItem.current;
Hunks? No more!
Write asynchronous update logic and trigger rerenders on demand. Only components subscribing to the updated atoms will rerender.
const useFetchData =
createSynergy(dataAtom, isLoadingAtom)
.createAction(() => async (data, isLoading) => {
isLoading.current = true;
// Trigger rerenders of all components
// reading from `isLoadingAtom`.
isLoading = isLoading.trigger();
const res = await fetch(url);
data.current = await res.json();
isLoading.current = false;
Global State.
Local State.

You can provide your state atoms high up in your component hierarchy to provide global state data such as user information or authentication data. Or you can inject atoms in a provider within small reusable components to provide contextual state within a small localized component hierarchy.

But most importantly, you can do both! Provide authentication data in a global synergy provider, and a list state in a localized provider within your list component for list items to consume. You can have many lists, each sharing the same atoms, but with their unique list state supplied by their respective provider. Also, you can define synergies that access both the localized list state and global authentication state.

Use for everything! Or some things.
3kB + immer

synergies uses immer for immutable data structures. Apart from that, only 3kB of gzipped code is added by using synergies.


Complete typesafety is provided by Typescript declarations.

Supports Middlewares

Use the well-known concept of middlewares to track errors, log state changes or debug issues.