Install Zustand X
mainTo use Zustand X, install it via pnpm. You must also have react and zustand installed in your project.
pnpm add zustand-xrepository·main·Indexed 19 days ago
https://github.com/udecode/zustand-xA Zustand store factory that provides a type-safe API for managing state. It reduces boilerplate by automating the creation of hooks, selectors, and actions, and offers high performance through auto-memoization and fine-grained reactivity. Features include specialized React hooks like useStoreValue and useTracked, support for vanilla stores via createVanillaStore, and integrated middleware for immer and mutative.
To use Zustand X, install it via pnpm. You must also have react and zustand installed in your project.
pnpm add zustand-xCreate a store using createStore and consume its values in React components using useStoreValue or useStoreState. Zustand X automatically provides type-safe access to state fields.
import { createStore, useStoreState, useStoreValue } from 'zustand-x';
// Create a store with an initial state
const repoStore = createStore({
name: 'ZustandX',
stars: 0,
});
// Use it in your components
function RepoInfo() {
const name = useStoreValue(repoStore, 'name');
const stars = useStoreValue(repoStore, 'stars');
return (
<div>
<h1>{name}</h1>
<p>{stars} stars</p>
</div>
);
}
function AddStarButton() {
const [, setStars] = useStoreState(repoStore, 'stars');
return <button onClick={() => setStars((s) => s + 1)}>Add star</button>;
}If you need to interact with the vanilla Zustand store directly, you can access it via the .store property on the Zustand X store instance.
useStoreSelect(store, selector) to use the original Zustand hook.store.store to get the vanilla store, which provides .getState(), .setState(), and .subscribe().// Use the original Zustand hook
const name = useStoreSelect(store, (state) => state.name);
// Get the vanilla store
const vanillaStore = store.store;
vanillaStore.getState();
vanillaStore.setState({ count: 1 });
const unsubscribe = vanillaStore.subscribe((state) =>
console.log('New state:', state)
);The store instance provides direct methods for interacting with state outside of React components.
store.get('key'): Get a single value.store.get('state'): Get the entire state.store.get('selector', ...args): Call a selector with arguments.store.set('key', value): Set a single value.store.set('action', ...args): Call an action.store.set('state', (draft) => { ... }): Update multiple values at once. If using immer or mutative, remember to return the draft to maintain compatibility.// Reading
store.get('name');
store.get('state');
store.get('someSelector', 1, 2);
// Writing
store.set('name', 'Bob');
store.set('someAction', 10);
store.set('state', (draft) => {
draft.name = 'Bob';
draft.loggedIn = true;
return draft;
});You can augment your store using extendSelectors and extendActions to add computed properties and reusable logic.
Use extendSelectors to derive new values. These are automatically memoized.
Use extendActions to define functions that modify state. You have access to get, set, and existing actions via the callback argument.
const extendedStore = store
.extendSelectors(({ get }) => ({
fullName: () => get('firstName') + ' ' + get('lastName'),
}))
.extendActions(({ get, set }) => ({
updateName: (newName: string) => set('name', newName),
resetState: () => {
set('state', (draft) => {
draft.firstName = 'Jane';
return draft;
});
},
}));
// Usage
extendedStore.get('fullName');
extendedStore.set('updateName', 'Julia');When calling createStore, you can pass a second argument to configure middleware like devtools, persist, immer, or mutative. Middleware can be enabled via a boolean or a detailed configuration object.
import { createStore } from 'zustand-x';
const userStore = createStore(
{
name: 'Alice',
loggedIn: false,
},
{
name: 'user',
devtools: true, // Enable Redux DevTools
persist: true, // Persist to localStorage
mutative: true, // Enable immer-style mutations
}
);Zustand X provides several hooks for React integration:
useStoreValue(store, key, ...args): Subscribes to a single value or selector. Supports custom equality functions.useStoreState(store, key, [equalityFn]): Returns [value, setter] similar to useState. Ideal for forms.useTracked(store, key): Subscribes to a value with minimal re-renders. Use this when you only need specific fields from a large object.useTrackedStore(store): Returns the entire store but only re-renders when the specific fields accessed in the component change.// useStoreValue
const name = useStoreValue(store, 'name');
const greeting = useStoreValue(store, 'greeting', 'Hello');
// useStoreState
const [name, setName] = useStoreState(store, 'name');
// useTracked
const user = useTracked(store, 'user'); // Only re-renders if user object changes
// useTrackedStore
const state = useTrackedStore(store); // Only re-renders when accessed fields changeUse store.subscribe to listen for changes. You can subscribe to specific keys, the entire state, or specific selectors.
store.subscribe('key', (val, prev) => ...): Listen to a specific field.store.subscribe('state', (state) => ...): Listen to the whole state.store.subscribe('selector', ...args, (result) => ...): Listen to a selector result.store.subscribe('key', selectorFn, callback, options): Subscribe with a custom selector and options like { fireImmediately: true }.// Subscribe to a field
const unsubscribe = store.subscribe('name', (name, previousName) => {
console.log('Name changed from', previousName, 'to', name);
});
// Subscribe to the entire state
const unsubscribe = store.subscribe('state', (state) => {
console.log('State changed:', state);
});
// Subscribe with a selector and options
const unsubscribe = store.subscribe(
'name',
name => name.length,
length => console.log('Name length changed:', length),
{ fireImmediately: true }
);You can provide an options object to mutativeMiddleware to configure the underlying mutative behavior. The available options are derived from PatchesOptions (excluding enablePatches).
Note that enablePatches is managed internally by the middleware to ensure compatibility with Zustand's state updates.
// Example of passing options to the middleware
const useStore = create<MyState>()(
mutativeMiddleware(
(set) => ({ ... }),
{ /* mutative options here */ }
)
);Zustand X uses a specific Prettier configuration to maintain code style and import ordering. The configuration enforces lf line endings, single quotes, a tab width of 2, and es5 trailing commas. It also utilizes the @ianvs/prettier-plugin-sort-imports plugin to automatically organize imports according to a predefined hierarchy (React/Next.js, third-party modules, internal aliases like @/, and relative imports).
module.exports = {
endOfLine: 'lf',
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
importOrder: [
'^(react/(.*)$)|^(react$)',
'^(next/(.*)$)|^(next$)',
'<THIRD_PARTY_MODULES>',
'',
'^types$',
'^@/types/(.*)$',
'^@/config/(.*)$',
'^@/lib/(.*)$',
'^@/hooks/(.*)$',
'^@/components/ui/(.*)$',
'^@/components/(.*)$',
'^@/registry/(.*)$',
'^@/styles/(.*)$',
'^@/app/(.*)$',
'',
'^[./]',
'',
'<TYPES>^[./]',
'<TYPES>^react',
'<TYPES>^next',
'<TYPES>^@/',
'<TYPES>',
],
importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
importOrderTypeScriptVersion: '5.1.6',
plugins: ['@ianvs/prettier-plugin-sort-imports'],
};You can pass an optional Options object as the second argument to immerMiddleware to customize Immer's behavior within your store.
import { create } from 'zustand';
import { immerMiddleware, type ImmerOptions } from 'zustand-x';
const useStore = create(
immerMiddleware(
(set) => ({
// ... state definition
}),
{
enableMapSet: true,
enabledAutoFreeze: true,
}
)
);{
enableMapSet?: boolean;
enabledAutoFreeze?: boolean;
}When creating a store with Zustand X, you can provide a TBaseStoreOptions object to configure the store's identity and its middleware behavior.
Key configuration properties include:
name: A required string used to identify the store.devtools: Configuration for the Devtools middleware.immer: Configuration for the Immer middleware.mutative: Configuration for the Mutative middleware.persist: Configuration for the Persist middleware, which accepts PersistOptions<StateType>.isMutativeState: A boolean flag. Set this to true if you are using custom middleware like immer or mutative and do not need the middleware to return a new state object.// Example of a configuration object for a store
const storeOptions = {
name: 'my-store',
devtools: { enabled: true },
immer: { /* immer options */ },
persist: { name: 'my-storage' },
isMutativeState: true
};