Gathering detailed insights and metrics for zustand-x
Gathering detailed insights and metrics for zustand-x
Gathering detailed insights and metrics for zustand-x
Gathering detailed insights and metrics for zustand-x
Zustand store factory for a best-in-class developer experience.
npm install zustand-x
Typescript
Module System
Node Version
NPM Version
94.5
Supply Chain
99.5
Quality
85.8
Maintenance
100
Vulnerability
99.6
License
zustand-x@6.1.1
Updated on Jun 17, 2025
zustand-x@6.1.0
Updated on Feb 15, 2025
zustand-x@6.0.3
Updated on Feb 05, 2025
zustand-x@6.0.2
Updated on Feb 05, 2025
zustand-x@6.0.1
Updated on Feb 05, 2025
zustand-x@6.0.0
Updated on Feb 04, 2025
TypeScript (75.13%)
JavaScript (24.87%)
Total Downloads
4,566,139
Last Day
5,598
Last Week
114,144
Last Month
510,532
Last Year
4,173,582
MIT License
420 Stars
247 Commits
28 Forks
10 Watchers
2 Branches
9 Contributors
Updated on Jun 25, 2025
Minified
Minified + Gzipped
Latest Version
6.1.0
Package Id
zustand-x@6.1.0
Unpacked Size
132.26 kB
Size
17.85 kB
File Count
8
NPM Version
10.8.2
Node Version
20.18.2
Published on
Feb 15, 2025
Cumulative downloads
Total Downloads
Last Day
1.1%
5,598
Compared to previous day
Last Week
-10.7%
114,144
Compared to previous week
Last Month
10.9%
510,532
Compared to previous month
Last Year
963.2%
4,173,582
Compared to previous year
1
An extension for Zustand that auto-generates type-safe actions, selectors, and hooks for your state. Built with TypeScript and React in mind.
store.get('name')
and store.set('name', value)
extendSelectors
extendActions
Built on top of zustand
, zustand-x
offers a better developer experience with less boilerplate. Create and interact with stores faster using a more intuitive API.
Looking for React Context-based state management instead of global state? Check out Jotai X - same API, different state model.
1pnpm add zustand-x
You'll also need react
and zustand
installed.
Here's how to create a simple store:
1import { createStore, useStoreState, useStoreValue } from 'zustand-x'; 2 3// Create a store with an initial state 4const repoStore = createStore({ 5 name: 'ZustandX', 6 stars: 0, 7}); 8 9// Use it in your components 10function RepoInfo() { 11 const name = useStoreValue(repoStore, 'name'); 12 const stars = useStoreValue(repoStore, 'stars'); 13 14 return ( 15 <div> 16 <h1>{name}</h1> 17 <p>{stars} stars</p> 18 </div> 19 ); 20} 21 22function AddStarButton() { 23 const [, setStars] = useStoreState(repoStore, 'stars'); 24 return <button onClick={() => setStars((s) => s + 1)}>Add star</button>; 25}
The store is where everything begins. Configure it with type-safe middleware:
1import { createStore } from 'zustand-x';
2
3// Types are inferred, including middleware options
4const userStore = createStore(
5 {
6 name: 'Alice',
7 loggedIn: false,
8 },
9 {
10 name: 'user',
11 devtools: true, // Enable Redux DevTools
12 persist: true, // Persist to localStorage
13 mutative: true, // Enable immer-style mutations
14 }
15);
Available middleware options:
1{ 2 name: string; 3 devtools?: boolean | DevToolsOptions; 4 persist?: boolean | PersistOptions; 5 immer?: boolean | ImmerOptions; 6 mutative?: boolean | MutativeOptions; 7}
The API is designed to be intuitive. Here's how you work with state:
1// Get a single value 2store.get('name'); // => 'Alice' 3 4// Get the entire state 5store.get('state'); 6 7// Call a selector with arguments 8store.get('someSelector', 1, 2);
1// Set a single value 2store.set('name', 'Bob'); 3 4// Call an action 5store.set('someAction', 10); 6 7// Update multiple values at once 8store.set('state', (draft) => { 9 draft.name = 'Bob'; 10 draft.loggedIn = true; 11});
1// Subscribe to changes 2const unsubscribe = store.subscribe('name', (name, previousName) => { 3 console.log('Name changed from', previousName, 'to', name); 4}); 5 6// Subscribe to the entire state 7const unsubscribe = store.subscribe('state', (state) => { 8 console.log('State changed:', state); 9}); 10 11// Subscribe to a selector with arguments 12const unsubscribe = store.subscribe('someSelector', 1, 2, (result) => { 13 console.log('Selector result changed:', result); 14}); 15 16// Subscribe with an additional selector and options 17const unsubscribe = store.subscribe( 18 'name', 19 name => name.length, 20 length => console.log('Name length changed:', length), 21 { fireImmediately: true } // Fire the callback immediately when subscribing 22);
useStoreValue(store, key, ...args)
Subscribe to a single value or selector. Optionally pass an equality function for custom comparison:
1const name = useStoreValue(store, 'name'); 2 3// With selector arguments 4const greeting = useStoreValue(store, 'greeting', 'Hello'); 5 6// With custom equality function for arrays/objects 7const items = useStoreValue( 8 store, 9 'items', 10 (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id) 11);
useStoreState(store, key, [equalityFn])
Get a value and its setter, just like useState
. Perfect for form inputs:
1function UserForm() {
2 const [name, setName] = useStoreState(store, 'name');
3 const [email, setEmail] = useStoreState(store, 'email');
4
5 return (
6 <form>
7 <input value={name} onChange={(e) => setName(e.target.value)} />
8 <input value={email} onChange={(e) => setEmail(e.target.value)} />
9 </form>
10 );
11}
useTracked(store, key)
Subscribe to a value with minimal re-renders. Perfect for large objects where you only use a few fields:
1function UserEmail() { 2 // Only re-renders when user.email changes 3 const user = useTracked(store, 'user'); 4 return <div>{user.email}</div>; 5} 6 7function UserAvatar() { 8 // Only re-renders when user.avatar changes 9 const user = useTracked(store, 'user'); 10 return <img src={user.avatar} />; 11}
useTrackedStore(store)
Get the entire store with tracking.
1function UserProfile() { 2 // Only re-renders when accessed fields change 3 const state = useTrackedStore(store); 4 5 return ( 6 <div> 7 <h1>{state.user.name}</h1> 8 <p>{state.user.bio}</p> 9 {state.isAdmin && <AdminPanel />} 10 </div> 11 ); 12}
Selectors help you derive new values from your state. Chain them together to build complex computations:
1const store = createStore( 2 { firstName: 'Jane', lastName: 'Doe' }, 3 { mutative: true } 4); 5 6const extendedStore = store 7 .extendSelectors(({ get }) => ({ 8 fullName: () => get('firstName') + ' ' + get('lastName'), 9 })) 10 .extendSelectors(({ get }) => ({ 11 fancyTitle: (prefix: string) => prefix + get('fullName').toUpperCase(), 12 })); 13 14// Using them 15extendedStore.get('fullName'); // => 'Jane Doe' 16extendedStore.get('fancyTitle', 'Hello '); // => 'Hello JANE DOE'
Use them in components:
1function Title() { 2 const fancyTitle = useStoreValue(extendedStore, 'fancyTitle', 'Welcome ') 3 return <h1>{fancyTitle}</h1> 4}
Actions are functions that modify state. They can read or write state and even compose with other actions:
1const storeWithActions = store.extendActions( 2 ({ get, set, actions: { someActionToOverride } }) => ({ 3 updateName: (newName: string) => set('name', newName), 4 resetState: () => { 5 set('state', (draft) => { 6 draft.firstName = 'Jane'; 7 draft.lastName = 'Doe'; 8 }); 9 }, 10 someActionToOverride: () => { 11 // You could call the original if you want: 12 // someActionToOverride() 13 // then do more stuff... 14 }, 15 }) 16); 17 18// Using actions 19storeWithActions.set('updateName', 'Julia'); 20storeWithActions.set('resetState');
Each middleware can be enabled with a simple boolean or configured with options:
1const store = createStore(
2 { name: 'ZustandX', stars: 10 },
3 {
4 name: 'repo',
5 devtools: { enabled: true }, // Redux DevTools with options
6 persist: { enabled: true }, // localStorage with options
7 mutative: true, // shorthand for { enabled: true }
8 }
9);
Access the underlying Zustand store when needed:
1// Use the original Zustand hook 2const name = useStoreSelect(store, (state) => state.name); 3 4// Get the vanilla store 5const vanillaStore = store.store; 6vanillaStore.getState(); 7vanillaStore.setState({ count: 1 }); 8 9// Subscribe to changes 10const unsubscribe = vanillaStore.subscribe((state) => 11 console.log('New state:', state) 12);
1// zustand 2import create from 'zustand' 3 4const useStore = create((set, get) => ({ 5 count: 0, 6 increment: () => set((state) => ({ count: state.count + 1 })), 7 // Computed values need manual memoization 8 double: 0, 9 setDouble: () => set((state) => ({ double: state.count * 2 })) 10})) 11 12// Component 13const count = useStore((state) => state.count) 14const increment = useStore((state) => state.increment) 15const double = useStore((state) => state.double) 16 17// zustand-x 18import { createStore, useStoreValue, useStoreState } from 'zustand-x' 19 20const store = createStore({ count: 0 }) 21 .extendSelectors(({ get }) => ({ 22 // Computed values are auto-memoized 23 double: () => get('count') * 2 24 })) 25 .extendActions(({ set }) => ({ 26 increment: () => set('count', (count) => count + 1), 27 })) 28 29// Component 30const count = useStoreValue(store, 'count') 31const double = useStoreValue(store, 'double') 32const increment = () => store.set('increment')
Key differences:
set('key', value)
patternextendSelectors
1// Before 2store.use.name(); 3store.get.name(); 4store.set.name('Bob'); 5 6// Now 7useStoreValue(store, 'name'); 8store.get('name'); 9store.set('name', 'Bob'); 10 11// With selectors and actions 12// Before 13store.use.someSelector(42); 14store.set.someAction(10); 15 16// Now 17useStoreValue(store, 'someSelector', 42); 18store.set('someAction', 10);
No vulnerabilities found.
No security vulnerabilities found.