Zustand Namespaces is a modular state management extension for Zustand, enabling a namespace-based approach for greater flexibility, especially in large state structures.
- Nested Middleware: Allows for nested middleware, making it easier to manage complex state structures.
- Single Store: All state is stored in a single store, making it easier to manage and debug.
npm install zustand-namespaces
import { create } from 'zustand';
import {
namespaced,
createNamespace,
getNamespaceHooks,
} from 'zustand-namespaces';
const namespaceA = createNamespace('namespaceA', () => ({
dataInNamespaceA: 'data',
}));
const namespaceB = createNamespace('namespaceB', () => ({
dataInNamespaceB: 'data',
}));
const useStore = create(
namespaced(
// Namespaced state is passed as an argument for you to use in your store
(namespacedState) => () => ({
mainData: 'data',
...namespacedState,
}),
{
namespaces: [namespaceA, namespaceB],
}
)
);
export const { namespaceA: useNamespaceA, namespaceB: useNamespaceB } =
getNamespaceHooks(useStore, namespaceA, namespaceB);
There are two state structure approaches that can be taken when using Zustand Namespaces: nested and flattened.
You can choose which approach to use by setting the flatten
option to true
when creating a namespace.
const namespaceA = createNamespace(
'namespaceA',
() => ({
dataInNamespaceA: 'data',
}),
{
flatten: true,
// Add a custom separator for flattened namespaces
separator: '_',
}
);
This is the default approach, where each namespace is nested within sub-objects.
{
namespaceA: {
dataInNamespaceA: 'data',
},
namespaceB: {
dataInNamespaceB: 'data',
},
mainData: 'data',
}
This approach is a good choice when you want to keep your state structure clean and organized. However, it can be more difficult to access and update state values. This method also inhibits the use of parent middleware to access child state (e.g., you can't partialize a subnamespaces state).
This approach flattens your namespace into the parent object.
{
namespaceA_dataInNamespaceA: 'data',
namespaceB_dataInNamespaceB: 'data',
mainData: 'data',
}
This approach is a good choice when you want to easily access and update state values. However, it can make your state structure harder to read. This method also allows for parent middleware to access child state (e.g., you can partialize a subnamespaces state).
Zustand Namespaces is fully typed for better state safety. See the examples for TypeScript implementations.
Integrates with any Zustand middleware. See the example for usage.
Due to the way some middleware was written, it may not work as expected with Zustand Namespaces. When in doubt, try the middleware in parallel rather than in series. The flexibility of Zustand Namespaces allows for middleware to be written in parallel with no drawbacks.
Here is an example of the persist
middleware that works in parallel and not in series:
✅persist
in parallel✅
const subNamespace = createNamespace(
'subNamespace',
persist(() => ({ foo: 'bar' }), { name: 'subNamespace' })
);
const subNamespace2 = createNamespace(
'subNamespace2',
persist(() => ({ data: 'hi' }), { name: 'subNamespace2' })
);
const namespace = createNamespace(
'namespace',
namespaced(
(namespacedState) => () => ({
...namespacedState,
}),
{
namespaces: [subNamespace, subNamespace2],
}
)
);
const useStore = create(namespaced({ namespaces: [namespace] }));
🛑persist
in series🛑
const subNamespace = createNamespace(
'subNamespace',
persist(() => ({ foo: 'bar' }), { name: 'subNamespace' })
);
const namespace = createNamespace(
'namespace',
namespaced(
(namespacedState) =>
persist(
() => ({
data: 'hi',
...namespacedState,
}),
{
name: 'namespace',
partialize: (state) => ({ data: state.data }),
}
),
{
namespaces: [subNamespace],
}
)
);
const useStore = create(namespaced({ namespaces: [namespace] }));
Not all middleware will have this issue, but it is something to be aware of when using Zustand Namespaces.
More examples can be found in the examples directory, covering various use cases including third-party integrations.
- createNamespace: Creates a new state namespace.
- namespaced: The middleware used to join namespaces.
- toNamespace: Extracts a namespace's state from some parent state.
- fromNamespace: Converts namespace state to some parent state.
- getNamespaceHooks: Returns hooks for each namespace.
- ExtractNamespace: Extracts a namespace type from a namespace.
- ExtractNamespaces: Extracts all namespace types from a list of namespaces.