Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Jotai store factory for a best-in-class developer experience.

License

NotificationsYou must be signed in to change notification settings

udecode/jotai-x

Repository files navigation

An extension forJotai that auto-generates type-safe hooks and utilities for your state. Built with TypeScript and React in mind.

Features

  • 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 usingextend
  • Built-in support for hydration, synchronization, and scoped providers

Why

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.

Installation

pnpm add jotai jotai-x

Quick Start

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>;}

Core Concepts

Store Configuration

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)}`),}),infiniteRenderDetectionLimit:100,// Optional render detection limit});

Available options:

{  name:string;  delay?:number;  effect?:React.ComponentType;  extend?:(atoms:Atoms)=>DerivedAtoms;  infiniteRenderDetectionLimit?:number;}

Store API

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'});

Reading and Writing State

There are three ways to interact with the store state:

1. Hooks (Recommended)

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(),},[]);

2. Store Instance Methods

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

3. Raw Atom Access

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;

Hook API Reference

use<Name>Value(key, options?)

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},[]);

use<Name>Set(key)

Get a setter function for a value:

constsetName=useAppSet('name');setName('new value');setName((prev)=>prev.toUpperCase());

use<Name>State(key)

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>);}

Provider-Based Store Hydration

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>);}

Scoped Providers

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');}

Derived Atoms

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 value
    • selector: Transform the atom value (must be memoized or use deps)
    • equalityFnOrDeps: Custom comparison function or deps array
    • deps: Dependencies array when using both selector and equalityFn
  • useSetAtom(store, atom): Get a setter function for a writable atom
  • useAtomState(store, atom): Get both value and setter for a writable atom, like React'suseState

Troubleshooting

Infinite Render Detection

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');

Migration from v1 to v2

// 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');

License

MIT

About

Jotai store factory for a best-in-class developer experience.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp