- Notifications
You must be signed in to change notification settings - Fork4
Jotai store factory for a best-in-class developer experience.
License
udecode/jotai-x
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
An extension forJotai that auto-generates type-safe hooks and utilities for your state. Built with TypeScript and React in mind.
- Auto-generated type-safe hooks for each state field
- Simple patterns:
use<StoreName>Value(key)
anduse<StoreName>Set(key, value)
- Extend your store with computed values using
extend
- Built-in support for hydration, synchronization, and scoped providers
Built on top ofjotai
,jotai-x
offers a better developer experience with less boilerplate. Create and interact with stores faster using a more intuitive API.
Looking for global state management instead of React Context-based state? Check outZustand X - same API, different state model.
pnpm add jotai jotai-x
Here's how to create a simple store:
import{createAtomStore}from'jotai-x';// Create a store with an initial state// Store name is used as prefix for all returned hooks (e.g., `useAppStore`, `useAppValue` for `name: 'app'`)const{ useAppStore, useAppValue, useAppSet, useAppState, AppProvider}=createAtomStore({name:'JotaiX',stars:0,},{name:'app',});// Use it in your componentsfunctionRepoInfo(){constname=useAppValue('name');conststars=useAppValue('stars');return(<div><h1>{name}</h1><p>{stars} stars</p></div>);}functionAddStarButton(){constsetStars=useAppSet('stars');return<buttononClick={()=>setStars((s)=>s+1)}>Add star</button>;}
The store is where everything begins. Configure it with type-safe options:
import{createAtomStore}from'jotai-x';// Types are inferred, including optionsconst{ useUserValue, useUserSet, useUserState, UserProvider}=createAtomStore({name:'Alice',loggedIn:false,},{name:'user',delay:100,// Optional delay for state updateseffect:EffectComponent,// Optional effect componentextend:(atoms)=>({// Optional derived atomsintro:atom((get)=>`My name is${get(atoms.name)}`),}),});
Available options:
{ name:string; delay?:number; effect?:React.ComponentType; extend?:(atoms:Atoms)=>DerivedAtoms;}
ThecreateAtomStore
function returns an object with the following:
const{// Store name used as prefixname:string,// Store hook returning all utilitiesuseAppStore:()=>StoreApi,// Direct hooks for state managementuseAppValue:(key:string,options?)=>Value,useAppSet:(key:string)=>SetterFn,useAppState:(key:string)=>[Value,SetterFn],// Provider componentAppProvider:React.FC<ProviderProps>,// Record of all atoms in the storeappStore:{atom:Record<string,Atom>}}=createAtomStore({ ...},{name:'app'});
There are three ways to interact with the store state:
The most straightforward way using hooks returned bycreateAtomStore
:
// Get valueconstname=useAppValue('name');conststars=useAppValue('stars');// Set valueconstsetName=useAppSet('name');constsetStars=useAppSet('stars');// Get both value and setterconst[name,setName]=useAppState('name');const[stars,setStars]=useAppState('stars');// With selector and depsconstupperName=useAppValue('name',{selector:(name)=>name.toUpperCase(),},[]);
Using the store instance fromuseAppStore()
:
conststore=useAppStore();// By keystore.get('name');// Get valuestore.set('name','value');// Set valuestore.subscribe('name',(value)=>console.log(value));// Subscribe to changes// Direct accessstore.getName();// Get valuestore.setName('value');// Set valuestore.subscribeName((value)=>console.log(value));// Subscribe to changes
For advanced use cases, you can work directly with atoms:
conststore=useAppStore();// Access atomsstore.getAtom(someAtom);// Get atom valuestore.setAtom(someAtom,'value');// Set atom valuestore.subscribeAtom(someAtom,(value)=>{});// Subscribe to atom// Access underlying Jotai storeconstjotaiStore=store.store;
Subscribe to a single value with optional selector and deps:
// Basic usageconstname=useAppValue('name');// With selectorconstupperName=useAppValue('name',{selector:(name)=>name.toUpperCase(),},[]// if selector is not memoized, provide deps array);// With equality functionconstname=useAppValue('name',{selector:(name)=>name,equalityFn:(prev,next)=>prev.length===next.length},[]);
Get a setter function for a value:
constsetName=useAppSet('name');setName('new value');setName((prev)=>prev.toUpperCase());
Get both value and setter, like React'suseState
:
functionUserForm(){const[name,setName]=useAppState('name');const[email,setEmail]=useAppState('email');return(<form><inputvalue={name}onChange={(e)=>setName(e.target.value)}/><inputvalue={email}onChange={(e)=>setEmail(e.target.value)}/></form>);}
The provider component handles store initialization and state synchronization:
typeProviderProps<T>={// Initial values for atoms, hydrated once on mountinitialValues?:Partial<T>;// Dynamic values for controlled state ...Partial<T>;// Optional custom store instancestore?:JotaiStore;// Optional scope for nested providersscope?:string;// Optional key to reset the storeresetKey?:any;children:React.ReactNode;};functionApp(){return(<UserProvider// Initial values hydrated on mountinitialValues={{name:'Alice',email:'alice@example.com'}}// Controlled values that sync with the storename="Bob"// Optional scope for nested providersscope="user1"// Optional key to reset store stateresetKey={version}><UserProfile/></UserProvider>);}
Create multiple instances of the same store with different scopes:
functionApp(){return(<UserProviderscope="parent"name="Parent User"><UserProviderscope="child"name="Child User"><UserProfile/></UserProvider></UserProvider>);}functionUserProfile(){// Get parent scopeconstparentName=useUserValue('name',{scope:'parent'});// Get closest scopeconstname=useUserValue('name');}
Two ways to create derived atoms:
// 1. Using extendconst{ useUserValue}=createAtomStore({name:'Alice',},{name:'user',extend:(atoms)=>({intro:atom((get)=>`My name is${get(atoms.name)}`),}),});// Access the derived value using the store nameconstintro=useUserValue('intro');// 2. External atomsconst{ userStore, useUserStore}=createAtomStore({name:'Alice',},{name:'user',});// Create an external atomconstintroAtom=atom((get)=>`My name is${get(userStore.atom.name)}`);// Create a writable external atomconstcountAtom=atom((get)=>get(userStore.atom.name).length,(get,set,newCount:number)=>{set(userStore.atom.name,'A'.repeat(newCount));});// Get the store instanceconststore=useUserStore();// Access external atoms using store-based atom hooksconstintro=useAtomValue(store,introAtom);// Read-only atomconst[count,setCount]=useAtomState(store,countAtom);// Read-write atomconstsetCount2=useSetAtom(store,countAtom);// Write-only// With selector and depsconstupperIntro=useAtomValue(store,introAtom,(intro)=>intro.toUpperCase(),[]// Optional deps array for selector);// With selector and equality functionconstintro2=useAtomValue(store,introAtom,(intro)=>intro,(prev,next)=>prev.length===next.length// Optional equality function);
The store-based atom hooks provide more flexibility when working with external atoms:
useAtomValue(store, atom, selector?, equalityFnOrDeps?, deps?)
: Subscribe to a read-only atom valueselector
: Transform the atom value (must be memoized or use deps)equalityFnOrDeps
: Custom comparison function or deps arraydeps
: Dependencies array when using both selector and equalityFn
useSetAtom(store, atom)
: Get a setter function for a writable atomuseAtomState(store, atom)
: Get both value and setter for a writable atom, like React'suseState
When using value hooks with selectors, ensure they are memoized:
// ❌ Wrong - will cause infinite rendersuseUserValue('name',{selector:(name)=>name.toUpperCase()});// ✅ Correct - memoize with useCallbackconstselector=useCallback((name)=>name.toUpperCase(),[]);useUserValue('name',{ selector});// ✅ Correct - provide deps arrayuseUserValue('name',{selector:(name)=>name.toUpperCase()},[]);// ✅ Correct - no selectoruseUserValue('name');
// Beforeconst{ useAppStore}=createAtomStore({name:'Alice'},{name:'app'});constname=useAppStore().get.name();constsetName=useAppStore().set.name();const[name,setName]=useAppStore().use.name();// Nowconst{ useAppStore, useAppValue, useAppSet, useAppState}=createAtomStore({name:'Alice'},{name:'app'});constname=useAppValue('name');constsetName=useAppSet('name');const[name,setName]=useAppState('name');
About
Jotai store factory for a best-in-class developer experience.
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors4
Uh oh!
There was an error while loading.Please reload this page.