# zustand · GPT-6 Luna # React quick start Install Zustand, create a store hook, and use it in a mounted React component to display and update selected state. Your store is a hook: create it with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create), select the state a component needs, and the component re-renders when that selection changes. ## Prerequisites Use a React 18 or later project. For TypeScript, install `@types/react` 18 or later. Zustand lists both as peer dependencies; the example also uses `react-dom` to mount the app in a browser. ## Install Zustand From your React project, run: ```bash npm install zustand ``` ## Steps ## 1. Create the store hook Create `CounterApp.tsx`. The store contains a count and an action that increments it. Calling `set` with the changed field updates the count while retaining the rest of the store. ```tsx title="CounterApp.tsx" import { create } from 'zustand' type CounterState = { count: number addOne: () => void } const useCounterStore = create((set) => ({ count: 0, addOne: () => set((state) => ({ count: state.count + 1 })), })) export function CounterApp() { const count = useCounterStore((state) => state.count) const addOne = useCounterStore((state) => state.addOne) return (

Count: {count}

) } ``` Each hook call selects one value from the store. The count selection renders the current number; clicking **Add one** calls the action, updates `count`, and causes the component to render the new number. You do not need a provider to use this hook. ## 2. Mount the component Render `CounterApp` from your React entry point. This `main.tsx` expects an HTML element with `id="root"` in the page. ```tsx title="main.tsx" import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { CounterApp } from './CounterApp' const rootElement = document.getElementById('root')! createRoot(rootElement).render( , ) ``` With both files mounted, the page shows `Count: 0` and an **Add one** button. Each click increments the displayed count. ![The mounted counter displays Count: 0 above the Add one button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/2c0571b62cc0e386f0839e441509abad.png) ## State updates and selectors `set` merges the returned fields one level deep. For updates to nested objects, see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state). For computed object or array selections, see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently). At the end, you have a mounted counter whose component subscribes to selected store state and updates when its action changes that state. ## Where to go next Open the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see a running application. Read [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works) for the store and selector model, or [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store) for more store patterns. # How Zustand works Zustand puts state and its update functions in a store, then exposes that store through a React hook. ## Your store is a hook Create a store with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) and use its returned hook inside components. Select the state each component needs. Zustand recommends colocating update functions, or actions, with the state they update, but does not require dispatched actions or reducers. The returned hook also carries store API utilities for non-component access. For component reads, use the hook's selector to focus on the value the component needs. ## How updates flow The component reads a selected value from the store hook. An action calls `set` with an immutable update; Zustand merges the changed fields and notifies subscribers, and React reads the selection again. ```mermaid flowchart LR A["Component selects state"] --> B["Store hook"] B --> C["Selected value renders"] D["Component calls action"] --> E["set() with immutable update"] E --> F["Store merges and notifies"] F --> B ``` ## Keep updates immutable Use `set` or `setState` to update state. For a flat update, return only the changed fields: Zustand merges that partial state with the current state. The merge is shallow and covers one level, so copy a nested object yourself when changing one of its fields. Actions can live in the store; reducers and dispatch are optional patterns. ## Select what a component needs Select the smallest value the component needs. Atomic selections use strict equality by default. If a selector constructs an object or array from several state values, [`useShallow`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-react-shallow#useshallow) keeps a shallowly equal result stable so unchanged selections do not cause an unnecessary re-render. ## See the pieces together This mounted React example renders a counter, a second readout through [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore), and an **Add one** button. The `inc` action updates the count through `set`; clicking the button updates the count in both components. Install the packages used by the React examples. React is also required by the `zustand/react/shallow` entry point: ```bash npm install zustand react react-dom ``` ```tsx import { createRoot } from 'react-dom/client' import { create, useStore } from 'zustand' import { useShallow } from 'zustand/react/shallow' type CounterState = { count: number inc: () => void } const useCounterStore = create()((set) => ({ count: 0, inc: () => set((state) => ({ count: state.count + 1 })), })) function Counter() { const { count, inc } = useCounterStore( useShallow((state) => ({ count: state.count, inc: state.inc })), ) return (

Count: {count}

) } function CountReadout() { const count = useStore(useCounterStore, (state) => state.count) return

Count via useStore: {count}

} function App() { return ( <> ) } const container = document.getElementById('root')! createRoot(container).render() ``` Provide a root element in the browser page where this entry runs: ```html
``` The state update returns only `count`; Zustand's shallow merge retains the `inc` action. The selector creates an object, and `useShallow` reuses its previous result when the selected fields remain shallowly equal. ## Pitfalls and next steps - For selectors that create new references, see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently). - For nested updates, see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state). - For the React setup and immutable update basics, see [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start). - For server-rendered Next.js applications, see [Set up Zustand in Next.js](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/set-up-zustand-in-nextjs). - Try the [live Zustand demo](https://zustand-demo.pmnd.rs/) to see a store-driven interface running. # React and vanilla stores A hook-bound store is the React hook with store API utilities returned by [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create). A vanilla store is a [`StoreApi`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#storeapi) returned by [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore), without a React hook. ## How the two stores differ Create a store without a React hook with [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore), imported from `zustand/vanilla`. It returns a [`StoreApi`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#storeapi): an object with `setState`, `getState`, `getInitialState` and `subscribe`. Use it from non-React code, or pass it to [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore) when a React component needs to subscribe to it. ```mermaid flowchart LR A["create()"] --> B["Hook-bound store"] B --> C["React component calls the store hook"] D["createStore()"] --> E["Vanilla StoreApi"] E --> F["Non-React code uses store utilities"] E --> G["useStore(store, selector)"] G --> H["React component subscribes to selected state"] ``` Both store creators take a state creator callback and initialize a store from its return value. For the initializer's library type, see [`StateCreator`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#statecreator). In React, pass the vanilla store and a selector to `useStore` to read the selected value. When a selector creates an object or array, see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently). ## Choosing a store | Choose | When | What you get | | --- | --- | --- | | `create` | React components are the store's consumers. | A hook for selecting state in components, with store API utilities attached. | | `createStore` | Code outside React needs the store API, or the store must be supplied to a React hook. | A vanilla `StoreApi`; use `useStore` to subscribe to it from React. | The hook-bound store type is [`UseBoundStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#useboundstore), which combines the store hook with its API. Both stores can be read or updated outside components through their attached or returned utilities. For the middleware caveat around vanilla `setState` and `getState`, see [Subscribe outside React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/subscribe-outside-react). For an example of Zustand running, open the [live demo](https://zustand-demo.pmnd.rs/). Continue with [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store) or [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react). # Selector subscriptions A selector defines the value a component subscribes to, and Zustand re-renders that component when the selected value changes. Use a selector for the smallest state value the component needs; wrap a computed object or array selector with [`useShallow`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-react-shallow#useshallow) when its contents can stay shallowly equal while the selector creates a new reference. ## How selection controls rendering The store can change while a component's selected value stays the same. Zustand compares selector results with `Object.is` by default, so a primitive selection such as a number or string does not change when an unrelated field changes. A selector that constructs a new object or array produces a different reference, even when its entries are unchanged. `useShallow` keeps the previous result when the new result is shallowly equal. ```mermaid flowchart LR A["Store update"] --> B["Run component selector"] B --> C{"Object.is: same result?"} C -->|Yes| D["Keep current render"] C -->|No| E["Re-render component"] B --> F["useShallow selector"] F --> G{"Shallowly equal?"} G -->|Yes| D G -->|No| E ``` Selecting the whole store subscribes a component to all store updates. Selecting an atomic value narrows that subscription. When you need a computed selection, `useShallow` handles arrays or objects whose top-level contents are unchanged; it does not make nested values deeply equal. ## Keep a computed selection stable This mounted React example renders the bear names and Papa Bear's meal separately. Clicking the button changes the meal: the meal display updates, while the names selection remains shallowly equal and its component does not need to re-render. Install Zustand and React, including React DOM for the mounted example: ```bash npm install zustand react react-dom ``` ```tsx import { createRoot } from 'react-dom/client' import { create } from 'zustand' import { useShallow } from 'zustand/react/shallow' type Meals = { papaBear: string mamaBear: string littleBear: string } type Store = { meals: Meals changePapaBearMeal: () => void } const useMeals = create((set) => ({ meals: { papaBear: 'large porridge-pot', mamaBear: 'middle-size porridge pot', littleBear: 'A little, small, wee pot', }, changePapaBearMeal: () => set((state) => ({ meals: { ...state.meals, papaBear: 'a large pizza' }, })), })) function BearNames() { const names = useMeals(useShallow((state) => Object.keys(state.meals))) return

Bear names: {names.join(', ')}

} function PapaBearMeal() { const meal = useMeals((state) => state.meals.papaBear) return

Papa Bear's meal: {meal}

} function App() { const changePapaBearMeal = useMeals((state) => state.changePapaBearMeal) return (
) } createRoot(document.getElementById('root')!).render() ``` Add `
` to the page that loads this entry point. Before the click, the page shows the three names and Papa Bear's porridge-pot meal. After the click, the meal text changes to pizza; the names stay the same. The names selector creates a fresh key array after the store update, but its entries are unchanged, so `useShallow` returns the previous array reference and prevents an unnecessary render of `BearNames`. The meal selector returns a different string, so `PapaBearMeal` updates. ## When to use shallow comparison Use [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) to create the store hook, then select a value inside each component. Prefer direct selections for individual primitives or stable action references. Reach for `useShallow` when one component needs several values as an object or array, or a computed result such as `Object.keys(state.meals)`, and the top-level contents may remain equal across store updates. Avoid an allocating selector without `useShallow` when stable output matters: in v5 a selector that returns a new reference can trigger unnecessary renders and may cause an infinite loop. See [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) for that pitfall and other selector patterns. For immutable updates, see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state); mutating a prior state object does not notify subscribers as an immutable update does. For a comparison outside a component subscription, [`shallow`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-shallow#shallow) returns whether two values are shallowly equal; it is not the component hook's equality-argument API. In v5, `create` does not accept a custom equality function. Use `useShallow` for the shallow-equality case rather than passing a second comparator to the store hook. ## See it running The [Zustand live demo](https://zustand-demo.pmnd.rs/) shows a store-backed interface in the browser. For the authors' focused selector example, see [Prevent rerenders with `useShallow`](https://zustand.docs.pmnd.rs/learn/guides/prevent-rerenders-with-use-shallow). ## Related - [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works) - [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) - [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) # Middleware and composition Middleware wraps a state creator to extend how one Zustand store behaves, while slices let you build that store from smaller state creators. ## How the pieces fit [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) receives the composed state creator and returns one hook-backed store. A middleware such as [`devtools`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#devtools) wraps that creator; slice creators contribute their fields and actions to the same store. ```mermaid flowchart TD A["create()"] --> B["devtools()"] B --> C["Composed state creator"] C --> D["Bear slice"] C --> E["Fish slice"] C --> F["One store and hook"] ``` The state creator is the function that receives `set`, `get`, and the store API and returns initial state. The [`StateCreator`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#statecreator) type lets each slice return only its own fields while typing its arguments against the full store state. Compose the slices into one state creator, then apply middleware to that combined creator—not separately inside each slice. ## Compose slices and wrap the store Use slices when a growing store benefits from splitting its state and actions by feature. This browser example puts bear and fish state in separate creators, wraps the combined creator with `devtools`, and mounts a React component that displays both counts. Clicking either button updates the matching count in the same store. ```tsx import { create, type StateCreator } from 'zustand' import { devtools } from 'zustand/middleware' import { createRoot } from 'react-dom/client' interface BearSlice { bears: number addBear: () => void eatFish: () => void } interface FishSlice { fishes: number addFish: () => void } type StoreState = BearSlice & FishSlice const createBearSlice: StateCreator< StoreState, [['zustand/devtools', never]], [], BearSlice > = (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 })), eatFish: () => set((state) => ({ fishes: state.fishes - 1 })), }) const createFishSlice: StateCreator< StoreState, [['zustand/devtools', never]], [], FishSlice > = (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 })), }) export const useBoundStore = create()( devtools((...args) => ({ ...createBearSlice(...args), ...createFishSlice(...args), })), ) export function App() { const bears = useBoundStore((state) => state.bears) const fishes = useBoundStore((state) => state.fishes) const addBear = useBoundStore((state) => state.addBear) const addFish = useBoundStore((state) => state.addFish) return (

Bears: {bears}

Fish: {fishes}

) } const container = document.createElement('div') document.body.append(container) createRoot(container).render() ``` The `eatFish` action demonstrates that a slice can update a field owned by another slice: each creator receives the full store state through its typed `set` argument. The component selects individual values and actions from the combined hook, so the rendered counts reflect updates to the shared store. ## Middleware composition and typing Apply middleware at the combined-store boundary. Putting middleware in individual slices can cause unexpected issues. With multiple middleware, nest the wrappers directly inside `create` so TypeScript can infer their mutator types. For example, `devtools(`[`persist`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#persist)`(stateCreator, options))` composes persistence inside DevTools. Put `devtools` outermost in the chain because other middleware can modify `setState`; placing `devtools` last ensures its `setState` changes and added type parameter remain in place. When a slice uses middleware, include the middleware mutator tuple in its `StateCreator` type, as the example does for `devtools`. Keep middleware nesting directly inside `create`; a reusable function that bundles middleware requires more advanced types to preserve contextual inference. ## When to use `combine` [`combine`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#combine) is an alternative when you want the initial-state object and the action creator in one place and want TypeScript to infer the state. It merges the initial object and the creator's returned object shallowly. The authors caution that the creator's `get` and `set` types reflect the initial-state parameter rather than the merged result, so replacing state or relying on `Object.keys(get())` can be unsafe. Prefer typed slices when the store is organized into separate feature creators or when you want the full state type available to each slice. ## Related - [Compose a store from slices](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/compose-a-store-from-slices) for a focused slice-pattern walkthrough. - [Debug with Redux DevTools](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/debug-with-redux-devtools) for using DevTools with a store. - [Type a store with TypeScript](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/type-a-store-with-typescript) for the broader Zustand typing patterns. - Explore the [Zustand live demo](https://zustand-demo.pmnd.rs/). # Persisted state lifecycle Persistence adds a storage read and write around a Zustand store: the middleware saves selected state when it changes, then hydrates the store from saved values during initialization. ## How state moves through persistence [`persist`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#persist) wraps the store's state creator. On initialization, it reads the item identified by `name`, checks its version, optionally migrates it, and merges the stored state with the store's current state. When the store changes, it applies `partialize` and writes that state and the version to storage. ```mermaid flowchart LR A["Initial state creator"] --> B["persist reads storage by name"] B --> C{"Stored version matches?"} C -->|"No; migrate is configured"| D["migrate stored state"] C -->|"Yes, or no stored value"| E["Stored state (possibly absent)"] D --> F["merge with current state"] E --> F F --> G["Hydrated store"] G --> H["State update"] H --> I["partialize, then write to storage"] ``` The stored value has a `state` and an optional `version` field ([`StorageValue`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#storagevalue)). The default storage uses JSON and `localStorage`; the default merge is shallow, with stored top-level fields taking precedence over the current state's fields. If no stored value exists, the current state remains in place. ## Combining and evolving stored state - The default `merge` is shallow. If you persist a nested object but omit some of its fields, the stored nested object replaces the current nested object at that top-level key. Provide a custom `merge` when rehydration must preserve or combine nested fields. - Use `version` and `migrate` when a change makes stored data incompatible with the current state shape. A version mismatch without a migration leaves the stored value unused; a migration returns the state for the current version. - `skipHydration` prevents the initial automatic hydration. This lets an application control when to call `rehydrate()`, such as in a server-rendered application. - `onRehydrateStorage` provides a callback before hydration and an optional callback after hydration or an error. For the storage setup and partial-persistence examples, see [Persist state to storage](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/persist-state-to-storage). # Immutable state updates Zustand updates state immutably, and its `set` function merges the update into the store one level deep. Create a React-bound store with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create), then update it through the `set` function supplied to the store initializer. ## How the merge works Pass only changed top-level fields to `set`: Zustand shallowly merges that partial object with the current state. A nested object is one field at the store level, so changing one of its properties requires creating a new object for that nested branch and copying the sibling fields you want to keep. ```mermaid flowchart TD A["set({ profile: nextProfile })"] --> B["Shallow merge at store level"] B --> C["Other top-level fields stay in the store"] B --> D["profile is replaced by nextProfile"] D --> E["Copy profile and its changed nested branch"] ``` This React example mounts a store with flat, nested, `Map`, and `Set` state. Click the buttons to change the theme, increment the home visit count, and toggle whether the guide is selected. The screen shows each new value while retaining unrelated state. ```tsx import { createRoot } from 'react-dom/client' import { create } from 'zustand' type State = { profile: { name: string preferences: { theme: string density: string } } visits: Map selected: Set changeTheme: () => void incrementVisits: () => void toggleGuide: () => void } const useStore = create()((set) => ({ profile: { name: 'Ada', preferences: { theme: 'light', density: 'comfortable' }, }, visits: new Map([['home', 0]]), selected: new Set(['guide']), changeTheme: () => set((state) => ({ profile: { ...state.profile, preferences: { ...state.profile.preferences, theme: state.profile.preferences.theme === 'light' ? 'dark' : 'light', }, }, })), incrementVisits: () => set((state) => ({ visits: new Map(state.visits).set( 'home', (state.visits.get('home') ?? 0) + 1, ), })), toggleGuide: () => set((state) => { const selected = new Set(state.selected) if (selected.has('guide')) { selected.delete('guide') } else { selected.add('guide') } return { selected } }), })) function App() { const name = useStore((state) => state.profile.name) const theme = useStore((state) => state.profile.preferences.theme) const density = useStore((state) => state.profile.preferences.density) const visits = useStore((state) => state.visits) const guideSelected = useStore((state) => state.selected.has('guide')) const changeTheme = useStore((state) => state.changeTheme) const incrementVisits = useStore((state) => state.incrementVisits) const toggleGuide = useStore((state) => state.toggleGuide) return (

{name}'s preferences

Theme: {theme}; density: {density}

Home visits: {visits.get('home')}

Guide selected: {guideSelected ? 'yes' : 'no'}

) } const container = document.getElementById('root')! createRoot(container).render() ``` At first, the page shows Ada's light, comfortable preferences, zero home visits, and the guide as selected. Changing the theme preserves the density; each visit increments the displayed count; toggling the guide changes its displayed selection without changing the other values. ## Collections need new instances `Map` and `Set` are mutable: calling `set`, `add`, or `delete` changes a collection without changing its reference. Make a copy, change the copy, and put that new instance in the store, as the example does. This gives a component selecting the collection or one of its values a changed reference to observe. See the [Map and Set demo](https://stackblitz.com/edit/vitejs-vite-5cu5ddvx) for more collection examples. ## When you use Immer For nested updates, the [`immer`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware-immer#immer) middleware lets an update recipe use mutation syntax while producing an immutable update. If you use Immer with class objects, mark them with `[immerable] = true`; without that marker Immer can mutate the current state without a proxy, and Zustand can skip notifying subscriptions. ## Related - [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) for nested object update patterns. - [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) for selectors and avoiding unnecessary re-renders. - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) for the separate issue of mutating an object from a previous render. # Create and bind a React store Create a hook-based store, select the state a component needs, and update it through an action. Use this pattern for shared application state when components can use the store directly; the hook does not need a provider. The store can hold state and functions together, so colocate each update action with the state it changes. ## Create the store and render a consumer Install Zustand in your React project: ```bash npm install zustand ``` 1. Create a store with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create). Its initializer returns the initial state and actions. In this example, `increment` uses the current count to return the changed field. 2. Call the store hook inside a React component with a selector for each value it needs. The counter displays the selected count; clicking **+1** calls the selected action, updates the count, and causes the component to render the new value. 3. Mount the component in the browser. The initial display is `0`; each click on **+1** increases it by one. ```tsx import { create } from 'zustand' import { createRoot } from 'react-dom/client' type CounterStore = { count: number increment: () => void } const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })) function Counter() { const count = useCounterStore((state) => state.count) const increment = useCounterStore((state) => state.increment) return (
{count}
) } function App() { return } const container = document.getElementById('root')! createRoot(container).render() ``` The component subscribes to the values returned by its selectors. When `increment` calls `set` with a partial object, Zustand merges that object into the current state, keeping the action alongside the updated count. ## Options and pitfalls This pattern passes no configuration options to `create`; define the initial fields and actions in its store initializer. For separate state values, use separate selectors as above. If you need a computed object or array, see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently); for nested updates, see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state). If you use `set` with `replace: true`, it replaces the entire state rather than merging it. Include every state field and action the application still needs in the replacement value, or those fields are removed. ## See it running Try the [live Zustand demo](https://zustand-demo.pmnd.rs/). The repository's [React demo source](https://github.com/pmndrs/zustand/blob/d7a5583cffd80af515f7dfb69583c95cbdc9e2ce/examples/demo/src/App.jsx) creates a store and binds its counter component in the same way. ## Related - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) - [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works) - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) - [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) # Select state efficiently Use [`useShallow`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-react-shallow#useshallow) when a selector builds an object or array from multiple state values and that result can remain shallowly equal across store updates. The component then keeps the same selected reference instead of re-rendering for an unchanged selection. ## Select multiple values The store hook created with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) compares selector results with `Object.is` by default. Wrap a selector that creates a new object or array in `useShallow` when updates to other state leave its contents shallowly equal. Install Zustand and the React packages used by this example: ```bash npm install zustand react react-dom npm install --save-dev @types/react @types/react-dom ``` This React example selects two meal values in one object. The button changes a third value, so the meal summary stays selected as before and its render count does not increase; the separate meal display updates. ```tsx import { useRef } from 'react' import { createRoot } from 'react-dom/client' import { create } from 'zustand' import { useShallow } from 'zustand/react/shallow' const useMeals = create(() => ({ papaBear: 'large porridge-pot', mamaBear: 'middle-size porridge pot', littleBear: 'A little, small, wee pot', })) function MealSummary() { const { papaBear, mamaBear } = useMeals( useShallow((state) => ({ papaBear: state.papaBear, mamaBear: state.mamaBear, })), ) const renders = useRef(0) renders.current += 1 return (

Meal summary

{papaBear}; {mamaBear}

Meal summary renders: {renders.current}

) } function LittleBearMeal() { const meal = useMeals((state) => state.littleBear) return (

Little Bear's meal: {meal}

) } function App() { return (
) } const container = document.getElementById('app') if (!container) { throw new Error('Expected an element with id="app"') } createRoot(container).render() ``` Put a mount element in the page's HTML before loading this React entry point: ```html
``` ![The initial view shows the meal summary and its render count, Little Bear's meal, and the change button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/92d33a834d48189022981839f9394f30.png) Initially, the summary shows Papa Bear and Mama Bear's meals and a render count of `1`. Click **Change Little Bear's meal**: Little Bear's displayed meal changes, while the summary and its count stay the same. The selector still creates a new object on each evaluation, but its selected properties have not changed, so `useShallow` returns the previous shallowly equal selection. ## Choose a selector Use `useShallow` when an object or array returned by a selector may be shallowly equal to the previous result. Its argument is a selector `(state) => selectedValue`; it returns a selector you pass to the store hook. It does not prevent a render when a selected property itself changes. | Selector result | Example | What the selection tracks | | --- | --- | --- | | Object of picks | `({ papaBear: state.papaBear, mamaBear: state.mamaBear })` | The picked values; an update outside these values leaves the selection equal. | | Array of picks | `[state.papaBear, state.mamaBear]` | The values in order; a changed value or order changes the selection. | | Computed keys | `Object.keys(state)` | The resulting keys; changing a value without changing keys leaves the selection equal. | Shallow equality compares the top-level values, not nested contents. If a selected value changes, or the output is not shallowly equal, the component re-renders. A selector that returns a new reference without stabilizing it can cause unnecessary renders and, in v5, may cause an infinite loop. Use `useShallow` for shallowly equal outputs, or make the selector return a stable reference. ## See it running Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see a running Zustand app. ## Related - [Selector subscriptions](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/selector-subscriptions) - [Use custom equality functions](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-custom-equality-functions) - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) # Update nested state Use immutable spreads for a small nested update; use the [`immer`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware-immer#immer) middleware when copying every level obscures the change. Both approaches keep actions beside state in a store created with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create). ## Update each nested level with `set` `set` merges only the top-level state. For nested data, return a new object at each level on the path to the changed value and spread each existing object to preserve its other fields. The example mounts a counter with two buttons. The first uses explicit immutable copies; the second uses the Immer middleware's draft update. Either button increments the nested count shown on screen. 1. Install Zustand and Immer, which the middleware requires as a direct dependency: ```bash npm install zustand immer ``` 2. Put this in your React entry file, such as `src/main.tsx`. The HTML page needs a `
` mount point. ```tsx import { createRoot } from 'react-dom/client' import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' type State = { deep: { nested: { obj: { count: number } } } } type Actions = { incrementWithSpreads: () => void incrementWithImmer: () => void } const useCounterStore = create()( immer((set) => ({ deep: { nested: { obj: { count: 0 } } }, incrementWithSpreads: () => set((state) => ({ deep: { ...state.deep, nested: { ...state.deep.nested, obj: { ...state.deep.nested.obj, count: state.deep.nested.obj.count + 1, }, }, }, })), incrementWithImmer: () => set((state) => { state.deep.nested.obj.count += 1 }), })), ) function NestedCounter() { const count = useCounterStore((state) => state.deep.nested.obj.count) const incrementWithSpreads = useCounterStore( (state) => state.incrementWithSpreads, ) const incrementWithImmer = useCounterStore( (state) => state.incrementWithImmer, ) return (

Nested count: {count}

) } const container = document.getElementById('root') if (!container) { throw new Error('Missing #root mount point') } createRoot(container).render() ``` ![The rendered counter displays its nested count above the two increment buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/719ee89c8549d128168c5e1e5e0f03f9.png) After either button is clicked, the displayed nested count increases by one. The spread-based action explicitly copies `deep`, `nested`, and `obj`; the Immer action changes the draft at the target path. The component selects the count and each action, so a count change updates the rendered value. ## Choose the update style | Approach | What you write | Use it when | | --- | --- | --- | | Immutable update with `set` | Return a new object for every nested level on the changed path, spreading each existing object. | The nested update is short and the copies make the change clear. | | Immer middleware | Wrap the store initializer with `immer`, then mutate the draft in a `set` callback. | Explicit copies make a deeply nested update hard to read. | The middleware lets the `set` callback use mutable syntax while Immer produces the immutable update. You do not need to call `produce` yourself for this pattern. ## Pitfall Do not replace a nested object with only the changed field. Zustand's merge is shallow, so that replacement discards sibling fields in the nested object. Spread the existing nested value before overriding the field, as `incrementWithSpreads` does. For class instances used with Immer, see [Immutable state updates](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/immutable-state-updates). For selector reference stability, see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently). ## Live demo Try the authors' [updating-state demo](https://stackblitz.com/edit/vitejs-vite-j6bjdygu) to see nested updates in a running React app. ## Related - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) - [Immutable state updates](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/immutable-state-updates) - [Immer middleware reference](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware-immer) # Persist state to storage This page's React example keeps saved favorites across reloads, excludes the draft from storage, and migrates a renamed legacy field. For how persistence reads, writes, and rehydrates state, see [Persisted state lifecycle](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/persisted-state-lifecycle). ## When to use it Use persistence for state that should remain available after a reload, such as a user's saved favorites. The middleware writes state to storage and rehydrates it when the store initializes. By default it uses JSON-backed `localStorage`. ## Create a persisted React store Add the middleware to the store hook your component already uses. This example saves favorite names but leaves the editable draft out of storage. It also migrates a version 0 record whose saved names used the `savedItems` field. Create the hook with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) and wrap its state creator with `persist`: ```tsx title="Favorites.tsx" import { create } from 'zustand' import { persist } from 'zustand/middleware' type PersistedFavorites = { favorites: string[] } type FavoriteStore = PersistedFavorites & { draft: string setDraft: (draft: string) => void addFavorite: () => void } function isLegacyFavorites( value: unknown, ): value is { savedItems: string[] } { return ( typeof value === 'object' && value !== null && 'savedItems' in value && Array.isArray(value.savedItems) && value.savedItems.every((item: unknown) => typeof item === 'string') ) } const useFavoriteStore = create()( persist( (set, get) => ({ favorites: [], draft: '', setDraft: (draft) => set({ draft }), addFavorite: () => { const favorite = get().draft.trim() if (favorite.length > 0) { set((state) => ({ favorites: [...state.favorites, favorite], draft: '', })) } }, }), { name: 'favorites-storage', version: 1, partialize: (state) => ({ favorites: state.favorites }), migrate: (persistedState, version): PersistedFavorites => { if (version === 0 && isLegacyFavorites(persistedState)) { return { favorites: persistedState.savedItems } } return { favorites: [] } }, }, ), ) export function Favorites() { const favorites = useFavoriteStore((state) => state.favorites) const draft = useFavoriteStore((state) => state.draft) const setDraft = useFavoriteStore((state) => state.setDraft) const addFavorite = useFavoriteStore((state) => state.addFavorite) return (
    {favorites.map((favorite) => (
  • {favorite}
  • ))}
) } ``` ![The page shows the favorite input, save button, and an empty list.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/98aacb9bcfb4131ec706661d291a93bc.png) Mount the component from your React entry point as shown in [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start). The persistence-specific difference is that saved favorites return after a reload while the draft does not. The page renders an input, a **Save favorite** button, and a list. Enter a name and save it to add that name to the list and `favorites-storage`; reloading restores the list, while the draft starts empty. The persisted object contains only `favorites`, not the draft or the store's action functions. ## Choose the storage key and saved fields `name` is required and identifies this store's entry in storage. Keep it unique among persisted stores that share the same storage. `partialize` receives the full store state and returns the value to persist; the component still reads the full state in memory. By default, `partialize` returns the state unchanged, so provide it when only some fields belong in storage. The example uses default JSON-backed `localStorage`. To select a different browser storage such as `sessionStorage`, use [`createJSONStorage`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#createjsonstorage) as the `storage` option, for example `createJSONStorage(() => sessionStorage)`. The helper uses JSON serialization; it does not validate the shape of values read from storage. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `name` | `string` | Required | Selects the unique storage key for this store. | | `storage` | [`PersistStorage`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#persiststorage) | `createJSONStorage(() => localStorage)` | Chooses the storage used to read and write persisted state. `U` is the shape returned by `partialize`. | | `partialize` | `(state) => persistedState` | `(state) => state` | Filters the state before it is written. | | `version` | `number` | `0` | Tags the persisted value with a schema version. | | `migrate` | `(persistedState, version) => persistedState \| Promise` | Not set | Converts data when the stored version differs from the current version. | ## Migrate a changed schema Increase `version` when the stored shape changes. In the example, version 1 expects `favorites`, while version 0 stored the same list as `savedItems`. The migration checks the unknown value before reading its field and returns the new persisted shape. If an older version does not have a migration, the middleware does not use that stored value; add a migration for each older shape you intend to retain. The default JSON storage parses values without runtime shape validation. Validate persisted data in your migration or use a validating custom storage if stored data can be corrupt or untrusted. With asynchronous storage, rehydration occurs after the store's initial render; see [Handle persisted state hydration](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/handle-persisted-state-hydration) if the page depends on the restored value at load time. ## Options to consider - Use the `storage` option when the default `localStorage` does not fit. The library's `createJSONStorage` helper adapts a [`StateStorage`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#statestorage) such as `sessionStorage` to the JSON-backed storage interface. - Use `partialize` to exclude transient UI state or actions from the stored value. - Use `version` with `migrate` when you rename or reshape persisted fields; the migration must return the shape expected by the current store. For the author's examples of custom URL-backed storage, see the [Hash storage demo](https://stackblitz.com/edit/vitejs-vite-9vg24prg) and [Query storage demo](https://stackblitz.com/edit/vitejs-vite-hyc97ynf). For the store and selector model behind the component, see [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works); for delayed restoration from asynchronous storage, see [Handle persisted state hydration](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/handle-persisted-state-hydration). # Handle persisted state hydration Control when a React store reads persisted state and keep dependent UI behind a loading state until rehydration finishes. ## When to use this Use this pattern when your UI needs persisted values as soon as the page loads, especially with asynchronous storage or server rendering. Asynchronous storage is read after the store's initial render, so rendering from its initial defaults can briefly show the wrong state—for example, treating a persisted signed-in user as signed out. ## Defer hydration and gate the UI 1. Set `skipHydration: true` on [`persist`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#persist). The store starts from its initial state and waits for an explicit `rehydrate()` call. 2. Use `onRehydrateStorage` to update a hydration flag after rehydration finishes or encounters an error. Exclude that flag from persisted data so a previous run cannot make the UI appear ready before the current read completes. 3. In a mounted React component, call `rehydrate()` from an effect and render dependent content only when the flag is true. Build the store hook with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) and wrap its state creator with `persist`. Create `bear-store.ts`: ```ts title="bear-store.ts" import { create } from 'zustand' import { persist } from 'zustand/middleware' type BearStore = { bears: number hasHydrated: boolean setHasHydrated: (hasHydrated: boolean) => void addABear: () => void } export const useBearStore = create()( persist( (set, get) => ({ bears: 0, hasHydrated: false, setHasHydrated: (hasHydrated) => set({ hasHydrated }), addABear: () => set({ bears: get().bears + 1 }), }), { name: 'hydration-example', partialize: (state) => ({ bears: state.bears }), skipHydration: true, onRehydrateStorage: (state) => () => { state.setHasHydrated(true) }, }, ), ) ``` The persisted record contains only `bears`. The callback closes over the store state passed before the read and marks the store ready when the read finishes, including when storage reports an error. Create `BearCounter.tsx`: ```tsx title="BearCounter.tsx" import { useEffect } from 'react' import { useBearStore } from './bear-store' export function BearCounter() { const bears = useBearStore((state) => state.bears) const hasHydrated = useBearStore((state) => state.hasHydrated) const addABear = useBearStore((state) => state.addABear) useEffect(() => { void useBearStore.persist.rehydrate() }, []) if (!hasHydrated) { return

Loading saved bears…

} return (

Saved bears: {bears}

) } ``` Mount `BearCounter` from your React entry point using the `createRoot` pattern in [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start): import this component and render `` instead of `CounterApp`. Before the effect starts hydration, the component renders **Loading saved bears…**. After the persisted value is merged into the store and the callback marks hydration complete, it renders **Saved bears: _n_** and an **Add a bear** button. Clicking the button increments the displayed value and persists the new count. On an error, the callback also releases the loading view, which then displays the current store value. ## Options that control hydration | Option | Type | Default | What it does | | --- | --- | --- | --- | | `skipHydration` | `boolean \| undefined` | `false` | Prevents the automatic hydration call during store initialization when `true`; call `rehydrate()` when your app is ready. | | `onRehydrateStorage` | `(state: S) => ((state?: S, error?: unknown) => void) \| void` | `undefined` | The outer callback runs before the storage read and may return a callback that runs after hydration or an error. | In the callback type, `S` is the persisted store's state type. The manual trigger returns `Promise | void`; this sample uses it to start hydration in a React effect and uses the callback to update renderable state. The UI selects the hydration flag from the store, so React re-renders when the flag changes. ## Pitfalls - With asynchronous storage, the store is not hydrated at the initial render. Gate any UI that depends on restored values instead of treating the initial defaults as loaded data. - The post-rehydration callback receives an error when storage hydration fails. Decide what your app should show in that case; this example releases the loading state and leaves the current store values visible. - `hasHydrated()` is a non-reactive getter. Reading it during render does not subscribe the component to later hydration changes; use state such as the `hasHydrated` field in this example when rendering must update. ## Live demo Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) for a running application, then use the sample above to gate a view on persisted state. ## Related - [Persisted state lifecycle](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/persisted-state-lifecycle) explains how the middleware reads, merges, and writes state. - [Persist state to storage](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/persist-state-to-storage) covers storage setup and partial persistence. - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) shows the basic mounted store-hook pattern. # Use a vanilla store in React Create a standalone store, then use a React component to display and update a selected value from it. Use this pattern when non-React code updates state that a mounted React component displays; for how vanilla and hook-bound stores differ, see [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores). ## Create the store and mount its React consumer Install [`zustand`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand) in your React project; see [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) for the package entry-point distinctions, including [`zustand/vanilla`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-vanilla). ```bash npm install zustand ``` In `CounterApp.tsx`, [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore) owns the count and [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore) selects it for the mounted component. Unlike a hook-bound counter, this example has a browser timer update the store outside the component. ```tsx import { useEffect } from 'react' import { createRoot } from 'react-dom/client' import { useStore } from 'zustand' import { createStore } from 'zustand/vanilla' type CounterStore = { count: number } const counterStore = createStore()(() => ({ count: 0 })) function Counter() { const count = useStore(counterStore, (state) => state.count) useEffect(() => { const interval = window.setInterval(() => { counterStore.setState((state) => ({ count: state.count + 1 })) }, 1000) return () => window.clearInterval(interval) }, []) return (
{count}
) } function App() { return } const container = document.getElementById('root')! createRoot(container).render() ``` Add `
` to the page and use this file as your browser entry point. In the browser, the mounted output starts at `0` and advances once per second as the timer updates the store. The timer callback runs independently of React rendering; the selected count causes the component to display each update. ## Selector and store API For the comparison of vanilla and hook-bound store APIs, see [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores). This page focuses on selecting the value the mounted React view renders. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `useStore` selector | `(state: CounterStore) => U` | Identity selector when omitted | Returns the selected value for the component. This example selects `count`. | The initializer's `set` updates state immutably. For an object update such as `{ count: nextCount }`, Zustand shallowly merges the returned fields into the current state; keep nested updates immutable as well. The vanilla store's `subscribe` method is available for non-React subscribers and returns an unsubscribe function. ## Pitfalls - If middleware changes `set` or `get`, those changes do not apply to the vanilla store's `setState` and `getState` methods. See [Subscribe outside React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/subscribe-outside-react). - In Next.js, avoid sharing a global store across simultaneous server requests, and do not read or write it from React Server Components. See [Set up Zustand in Next.js](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/set-up-zustand-in-nextjs). ## See it running Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see Zustand running in a browser. ## Related - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store) - [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) - [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) # Compose a store from slices Split a growing store into feature-focused state creators, then combine them into one store your React components can use. Build the combined hook with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) and type each slice creator with [`StateCreator`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#statecreator). ## When to use slices Use slices when keeping every feature's state and actions in one large creator makes the store harder to maintain. A slice is a creator for part of the store's state; combining slices does not create separate stores for your components. Your components use the one combined hook and can select state from any slice. ## Define, combine, and render the slices 1. Give each feature a state type and creator. Spread the returned slice objects into one typed store, then render a component that selects from both slices. The example starts with the profile name **Jordan** and compact mode off. Its buttons rename the profile and toggle the preference. ```tsx import { create, type StateCreator } from 'zustand' interface ProfileSlice { profileName: string renameProfile: (name: string) => void } interface PreferencesSlice { compactMode: boolean toggleCompactMode: () => void } type SettingsStore = ProfileSlice & PreferencesSlice const createProfileSlice: StateCreator< SettingsStore, [], [], ProfileSlice > = (set) => ({ profileName: 'Jordan', renameProfile: (name) => set({ profileName: name }), }) const createPreferencesSlice: StateCreator< SettingsStore, [], [], PreferencesSlice > = (set) => ({ compactMode: false, toggleCompactMode: () => set((state) => ({ compactMode: !state.compactMode })), }) export const useSettingsStore = create()((...args) => ({ ...createProfileSlice(...args), ...createPreferencesSlice(...args), })) export function App() { const profileName = useSettingsStore((state) => state.profileName) const renameProfile = useSettingsStore((state) => state.renameProfile) const compactMode = useSettingsStore((state) => state.compactMode) const toggleCompactMode = useSettingsStore( (state) => state.toggleCompactMode, ) return (

Settings for {profileName}

Compact mode: {compactMode ? 'on' : 'off'}

) } ``` ![Look at the initial profile name, compact-mode value, and two controls rendered from the combined store.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/a7cda5160481577c13d69b637359caf6.png) The first `StateCreator` type is the full store state; the fourth is the slice returned by that creator. In this example, both creators use empty mutator tuples. The spreads combine the profile and preference values and actions into one store, so the component can select from either feature. Clicking **Rename to Casey** changes the heading; clicking **Toggle compact mode** changes the displayed preference. ## What to watch out for - Keep each slice creator focused on the state and actions for its feature. When one action needs to update fields from more than one slice, define that action against the combined store state. - Apply middleware to the combined store, not inside individual slice creators; applying middleware inside slices can lead to unexpected issues. See [Middleware and composition](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/middleware-and-composition) for middleware type arguments. - Keep updates immutable. See [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) for nested objects and [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) for why mutating previous state does not notify subscribers. - Select primitive values or stable references in components. See [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) for selectors that create new references. ## Related - [Type a store with TypeScript](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/type-a-store-with-typescript) - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Middleware and composition](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/middleware-and-composition) # Debug with Redux DevTools Connect a Zustand store to Redux DevTools and inspect its named state updates. Use this when you want a development timeline of store updates without adopting Redux. Wrap the store's state creator in [`devtools`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#devtools), then create the hook with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create). Install the Redux DevTools package and the browser extension before running the example: ```bash npm install zustand @redux-devtools/extension ``` Install the [Redux DevTools browser extension](https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) as well. ## Add the middleware to a React store For the basic store and component-binding pattern, see [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store). The difference here is the `devtools` wrapper and the third `set` argument: this complete `App.tsx` example renders an article status, and each button records a distinct action type in Redux DevTools. ```tsx import { createRoot } from 'react-dom/client' import { create } from 'zustand' import { devtools } from 'zustand/middleware' type ArticleStore = { status: 'draft' | 'published' publish: () => void reopen: () => void } const useArticleStore = create()( devtools((set) => ({ status: 'draft', publish: () => set({ status: 'published' }, undefined, 'article/publish'), reopen: () => set({ status: 'draft' }, undefined, 'article/reopen'), })), ) function ArticleStatus() { const status = useArticleStore((state) => state.status) const publish = useArticleStore((state) => state.publish) const reopen = useArticleStore((state) => state.reopen) return (

Article status: {status}

) } const container = document.getElementById('root')! createRoot(container).render() ``` ![The initial draft status appears above the Publish and Reopen buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/aeb75a4a9a09000ad1c2447b828f50b4.png) Open Redux DevTools and select the store to inspect its state and updates. Clicking **Publish** changes the rendered status to `published` and records `article/publish`; clicking **Reopen** changes it back to `draft` and records `article/reopen`. You can inspect either update or use DevTools time travel. ## Options to identify the connection Pass the optional second argument to `devtools` when you need a distinct connection name or want to control whether the integration runs. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `name` | `string` | Not specified | Sets a custom identifier for the Redux DevTools connection. | | `enabled` | `boolean` | `true` in development; `false` in production | Enables or disables the Redux DevTools integration for this store. | | `anonymousActionType` | `string` | Inferred action type, or `anonymous` if unavailable | Sets the action type used for mutations without a supplied action name. | | `store` | `string` | Not specified | Sets a custom identifier for this store in Redux DevTools. | For example, add `{ name: 'CounterStore' }` as the second argument to `devtools` to label the connection. Give updates meaningful action types in `set` as in the example; without a supplied name, DevTools uses an inferred action type when available and otherwise falls back to `anonymous`. ## Pitfalls - `devtools` requires the `@redux-devtools/extension` package as well as the browser extension. In production, the integration is disabled by default; set `enabled` only when you intentionally want a different setting. - Redux DevTools displays one store at a time by default. Use its store selector to switch to another store. - An update without an explicit action type can appear as `anonymous` when Zustand cannot infer a name. Add a third `set` argument, such as `'counter/increment'`, to make its timeline entry explicit. ## Live demo Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see Zustand running in the browser. ## Related - [Compose middleware](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/middleware-and-composition) - [Use Redux-style actions](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-redux-style-actions) - [Type a store with TypeScript](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/type-a-store-with-typescript) - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) # Use Redux-style actions Handle store updates through a reducer and dispatched actions when a reducer-based action pattern fits your application. Use this pattern when you want one reducer to describe how explicit action objects change a store. Zustand does not require reducers or dispatched actions; its recommended pattern is to keep actions with state and update through `set` or `setState`. ## Create a reducer-backed store The [`redux`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#redux) middleware takes a reducer and initial state, then returns a state creator with `dispatch`. Wrap it with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) to get a React store hook. 1. Define the state and action union. Each action has a string `type` and any data the reducer needs. 2. Write a pure reducer that takes the current state and action and returns the next state. 3. Pass the reducer and initial state to `redux`, then create a hook-backed store. The selected `dispatch` function accepts only the declared action union. ```tsx import { create } from 'zustand' import { redux } from 'zustand/middleware' import { createRoot } from 'react-dom/client' type AlertsState = { alertsEnabled: boolean } type AlertsAction = | { type: 'alerts/enable' } | { type: 'alerts/disable' } type AlertsStore = AlertsState & { dispatch: (action: AlertsAction) => AlertsAction } function alertsReducer( state: AlertsState, action: AlertsAction, ): AlertsState { switch (action.type) { case 'alerts/enable': return { alertsEnabled: true } case 'alerts/disable': return { alertsEnabled: false } default: return state } } const initialState: AlertsState = { alertsEnabled: false } const useAlertsStore = create()( redux(alertsReducer, initialState), ) function AlertsSetting() { const alertsEnabled = useAlertsStore((state) => state.alertsEnabled) const dispatch = useAlertsStore((state) => state.dispatch) return (

Alerts: {alertsEnabled ? 'On' : 'Off'}

) } const container = document.createElement('div') document.body.append(container) createRoot(container).render() ``` ![The initial Alerts: Off setting appears above the Enable alerts and Disable alerts buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/e3895c5f7a6b792908afe81e66b3238b.png) The mounted control starts at **Alerts: Off**. Clicking **Enable alerts** dispatches its action and changes the display to **Alerts: On**; **Disable alerts** dispatches the action that displays **Alerts: Off**. ## Reducer and middleware inputs `redux` has no configuration options here. It takes these two required inputs: | Input | Type | Default | Effect | | --- | --- | --- | --- | | `reducer` | `(state: T, action: A) => T` | None | Computes the next state from the current state and an action. Keep it pure. | | `initialState` | `T` | None | Provides the store's starting state; it cannot be a function. | The middleware returns a state creator. Its `dispatch` function is available in the store state and on the store API, and calling it returns the dispatched action. ## Pitfalls - Reducers and dispatched actions are optional in Zustand. For the colocated-action pattern without a reducer, see [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store). ## See it running Try the [Zustand live demo](https://zustand-demo.pmnd.rs/). ## Related - [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works) - [Middleware and composition](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/middleware-and-composition) - [Debug with Redux DevTools](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/debug-with-redux-devtools) # Initialize state from props Create a vanilla store from a provider's props and share that store with the React components beneath it. Use this pattern when a store needs values supplied by a component, such as a scoped feature or an injected dependency. Each mounted provider keeps its own store, and its descendants read and update that store through context. ## Create a store from provider props Install Zustand in your React project: ```bash npm install zustand ``` 1. Define the provider's input props and the store state, including its action. 2. Create a store factory with [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore) that combines defaults with the supplied props. The factory returns a vanilla store; the provider keeps that instance in state. 3. Put the store in React context. The consumer reads it from context and calls [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore) to select the count and action. 4. Mount the provider with an initial `bears` value. The page renders that count and an **Add bear** button; clicking the button increments the count shown by the consumer. ```tsx import { createRoot } from 'react-dom/client' import { createContext, useContext, useState } from 'react' import type { PropsWithChildren } from 'react' import { createStore, useStore } from 'zustand' type BearProps = { bears: number } type BearState = BearProps & { addBear: () => void } function createBearStore(initProps?: Partial) { const defaultProps: BearProps = { bears: 0 } return createStore()((set) => ({ ...defaultProps, ...initProps, addBear: () => set((state) => ({ bears: state.bears + 1 })), })) } type BearStore = ReturnType const BearContext = createContext(null) type BearProviderProps = PropsWithChildren function BearProvider({ children, ...props }: BearProviderProps) { const [store] = useState(() => createBearStore(props)) return {children} } function BearCounter() { const store = useContext(BearContext) if (!store) { throw new Error('BearCounter must be rendered inside BearProvider') } const bears = useStore(store, (state) => state.bears) const addBear = useStore(store, (state) => state.addBear) return (
{bears} Bears.
) } function App() { return ( ) } const container = document.getElementById('root')! createRoot(container).render() ``` ![The mounted counter shows the initial bear count and the Add bear button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/5e3d30592d56d4b5837f0a9489ec201d.png) Add `
` to the page and load this file from your React entry point. On mount, the consumer displays **2 Bears.**; each click on **Add bear** increases the displayed count by one. The context keeps the store scoped to the provider, while [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore) subscribes the consumer to its selected values. ## Inputs and behavior | Input | Type | Default | What it does | | --- | --- | --- | --- | | `initProps` | `Partial` | `undefined` | Supplies initial prop values for the store; the factory's default count is `0`, and provided values override it. | | `bears` | `number` | Required on `BearProvider` | Seeds the scoped store in this example. | | `useStore` selector | `(state: BearState) => U` | Identity selector if omitted | Returns the selected value so the component can render it or call the selected action. | The vanilla store is created with `createStore`, which returns the store API rather than a React hook. The provider holds one created instance for its mounted lifetime, and the context makes that instance available to descendants. `useStore` returns the selected current-state value and subscribes the component to changes in that selection. ## Pitfalls - Do not use a module-level store when each provider needs independent state; create the store in the provider's initializer as shown. - In Next.js, avoid sharing a global store across simultaneous server requests, and do not read or write a store from React Server Components. See [Set up Zustand in Next.js](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/set-up-zustand-in-nextjs). - For middleware caveats about vanilla store API utilities, see [Subscribe outside React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/subscribe-outside-react). ## See it running Open the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see Zustand running in a browser. ## Related - [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react) - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store) # Set up Zustand in Next.js Create a vanilla store per provider instance, expose it to client components, and mount the provider in either Next.js router without sharing a module-level store across server requests. ## When to use this Use a provider-owned store when Next.js server rendering and client components need to share application state. A module-level store can be shared by simultaneous server requests, so create the store inside the provider instead. ## Create the store and provider Install [`zustand`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand), which includes the vanilla entry point: ```bash npm install zustand ``` Create the vanilla store with [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore) from [`zustand/vanilla`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-vanilla). The factory accepts initial count data and keeps the increment and decrement actions with that state. ```ts title="src/stores/counter-store.ts" import { createStore } from 'zustand/vanilla' export type CounterState = { count: number } export type CounterActions = { decrementCount: () => void incrementCount: () => void } export type CounterStore = CounterState & CounterActions export const defaultInitState: CounterState = { count: 0, } export const createCounterStore = ( initState: CounterState = defaultInitState, ) => { return createStore()((set) => ({ ...initState, decrementCount: () => set((state) => ({ count: state.count - 1 })), incrementCount: () => set((state) => ({ count: state.count + 1 })), })) } ``` The provider creates its store with a lazy state initializer, so a re-render of that provider keeps the same store instance. Its context uses [`StoreApi`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#storeapi) to retain the store's state type. The custom hook reads the context and subscribes through [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore); it throws a clear error if a component renders outside the provider. ```tsx title="src/providers/counter-store-provider.tsx" 'use client' import { createContext, useContext, useState, type ReactNode } from 'react' import { useStore } from 'zustand' import type { StoreApi } from 'zustand/vanilla' import { createCounterStore } from '../stores/counter-store' type CounterStore = { count: number decrementCount: () => void incrementCount: () => void } type CounterStoreApi = StoreApi const CounterStoreContext = createContext(undefined) export function CounterStoreProvider({ children }: { children: ReactNode }) { const [store] = useState(() => createCounterStore()) return ( {children} ) } export function useCounterStore(selector: (state: CounterStore) => T): T { const store = useContext(CounterStoreContext) if (!store) { throw new Error('useCounterStore must be used within CounterStoreProvider') } return useStore(store, selector) } ``` Add a client component that selects the count and action it needs. It renders `Count: 0` initially; clicking **Increment Count** updates the displayed count, and **Decrement Count** lowers it. ```tsx title="src/components/counter.tsx" 'use client' import { useCounterStore } from '../providers/counter-store-provider' type CounterStore = { count: number decrementCount: () => void incrementCount: () => void } export function Counter() { const count = useCounterStore((state: CounterStore) => state.count) const incrementCount = useCounterStore( (state: CounterStore) => state.incrementCount, ) const decrementCount = useCounterStore( (state: CounterStore) => state.decrementCount, ) return (
Count: {count}
) } ``` ## Mount the provider Choose the mounting point for your router. A provider around the whole application shares one store instance within that provider tree. Put it at a route instead when that route needs its own store instance; do not create a new provider per route unless route-scoped state is required. ### App Router Render the client provider from the server root layout. The root layout stays a server component; the counter itself is a client component. ```tsx title="src/app/layout.tsx" import type { ReactNode } from 'react' import { CounterStoreProvider } from '../providers/counter-store-provider' export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ) } ``` Render the counter from the route page: ```tsx title="src/app/page.tsx" import { Counter } from '../components/counter' export default function Page() { return } ``` The page displays the counter inside the provider, and client-side button clicks update its selected state. ### Pages Router For route-scoped state, wrap the page content in the provider: ```tsx title="src/pages/index.tsx" import { CounterStoreProvider } from '../providers/counter-store-provider' import { Counter } from '../components/counter' export default function Home() { return ( ) } ``` The page shows the same initial count and buttons; each click updates the count through its provider-owned store. For an app-wide store, wrap the page component in the provider from the Pages Router's custom application component. ## Initial data and request boundaries The provider sample initializes its store with the factory's default `{ count: 0 }`. For route-specific state, place the provider at the route boundary. | Factory input | Type | Default | Effect | | --- | --- | --- | --- | | `initState` | `CounterState` | `defaultInitState` (`{ count: 0 }`) | Supplies the starting count for the store instance. | ## Pitfalls - Do not define one global store for the server to reuse across requests. Construct the store inside the provider so each provider instance owns its store. - Do not read or write the store from a React Server Component. Keep store access in client components. - Initialize server and client rendering with the same data; different initial output can cause hydration errors. See [Handle persisted-state hydration](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/handle-persisted-state-hydration) for the guide's hydration-specific task. - For vanilla-store details on `set`, `get`, `setState`, and `getState`, see [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react). ## See also - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react) - [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store) - [Zustand's Next.js setup guide](https://zustand.docs.pmnd.rs/learn/guides/nextjs) # Type a store with TypeScript Use a state type to check a store's fields and actions, then derive the complete store type for components and utilities. ## When to use this Use this pattern when you want TypeScript to check the store's initial state and action signatures, and want one reusable type that stays aligned with the store. Install [`zustand`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand) if it is not already in your project: ```bash npm install zustand ``` ## 1. Declare the state shape and create the store Pass your state-and-actions type to [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) in its curried form, `create()(creator)`. The type argument checks the object returned by the creator, while TypeScript infers the `set` callback from that type. The extra call lets you provide the state type while leaving the creator's other types inferred. Use [`ExtractState`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#extractstate) to derive and export the complete state-and-actions type from the bound store. ```ts title="store.ts" import { create, type ExtractState } from 'zustand' interface BearState { bears: number food: string feed: (food: string) => void } export const useBearStore = create()((set) => ({ bears: 2, food: 'honey', feed: (food) => set({ food }), })) export type BearStoreState = ExtractState ``` The store starts with two bears and honey. TypeScript checks that the returned object supplies both state fields and the `feed` action, and checks the action's argument as a string. ## 2. Reuse the store type The exported alias gives utilities the full state-and-actions type without a second version of the store's shape. ```ts title="summary.ts" import type { BearStoreState } from './store' export function describeBears(state: BearStoreState): string { return `${state.bears} bears eat ${state.food}` } ``` In a React component, read the store and pass it to the typed utility. The button calls the colocated action; the displayed summary changes from honey to berries. ```tsx title="App.tsx" import { useBearStore } from './store' import { describeBears } from './summary' export function App() { const state = useBearStore() return (

{describeBears(state)}

) } ``` For the React entry-point code that mounts `App`, use [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start#2-mount-the-component); this page adds the typed store contract and reusable state-and-actions type. ## What to watch for - Keep state updates immutable. If you update a nested object, copy its existing fields; see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state). - When a component needs only part of the state, select that part rather than subscribing to the whole store; see [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently). - Do not call `get` synchronously while the initial state creator is running: the state has not been created yet. Use it from an action after store creation instead. ## See it running Open the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see a running Zustand application. ## Related - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) - [How Zustand works](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/how-zustand-works) - [Compose a store from slices](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/compose-a-store-from-slices) # Reset a store Reset a store to the state it had when it was created, or register reset callbacks to reset several stores together. ## Reset one store Use a colocated `reset` action when one store owns the reset. This example focuses on the reset action: for the mounted counter-and-button setup, see [Debug with Redux DevTools](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/debug-with-redux-devtools). The mounted editor below changes its draft, then restores the original text when you click **Reset**. Put this in `src/main.tsx` in a React app with a `
` mount point. Create the store with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create), then render a component that selects the draft and its actions: ```tsx title="src/main.tsx" import { createRoot } from 'react-dom/client' import { create } from 'zustand' type DraftStore = { draft: string edit: () => void reset: () => void } const useDraftStore = create()((set, _get, store) => ({ draft: 'Draft starts here', edit: () => set({ draft: 'Edited draft' }), reset: () => set(store.getInitialState()), })) function DraftEditor() { const draft = useDraftStore((state) => state.draft) const edit = useDraftStore((state) => state.edit) const reset = useDraftStore((state) => state.reset) return (

Draft: {draft}

) } const rootElement = document.getElementById('root') if (!rootElement) { throw new Error('Missing #root mount point') } createRoot(rootElement).render() ``` ![The mounted editor displays its initial draft above the Edit draft and Reset buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/1aa71d2fcd3f54f15f6c6f77179221db.png) The `reset` action returns no value and restores the draft to its initial text. `getInitialState()` reads the state captured when the store is created; the action follows the authors' reset pattern of passing that state to `set`. ## Reset multiple stores For a coordinated reset, keep a set of callbacks and add one callback for each store. This React example starts two counters at different values. Clicking **Reset all** calls every registered callback, and both displayed counts return to their initial values. Put this alternative in `src/main.tsx`: ```tsx title="src/main.tsx" import { createRoot } from 'react-dom/client' import { create } from 'zustand' type CounterStore = { count: number increment: () => void } const useFirstStore = create()((set) => ({ count: 1, increment: () => set((state) => ({ count: state.count + 1 })), })) const useSecondStore = create()((set) => ({ count: 10, increment: () => set((state) => ({ count: state.count + 1 })), })) const storeResetFns = new Set<() => void>() storeResetFns.add(() => useFirstStore.setState(useFirstStore.getInitialState(), true), ) storeResetFns.add(() => useSecondStore.setState(useSecondStore.getInitialState(), true), ) function resetAllStores() { storeResetFns.forEach((resetFn) => { resetFn() }) } function Counters() { const firstCount = useFirstStore((state) => state.count) const incrementFirst = useFirstStore((state) => state.increment) const secondCount = useSecondStore((state) => state.count) const incrementSecond = useSecondStore((state) => state.increment) return (

First count: {firstCount}

Second count: {secondCount}

) } const rootElement = document.getElementById('root') if (!rootElement) { throw new Error('Missing #root mount point') } createRoot(rootElement).render() ``` ![The mounted page displays two counters and buttons to increment each counter or reset both.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/a167efb730032f938c0a624f829f7b34.png) Each registered callback calls `setState` with that store's initial state and `true`, so the state is replaced rather than shallowly merged. `resetAllStores()` returns no value; it invokes every callback in the set, and the mounted components render the restored counts. ## Pitfalls - The single-store recipe calls `set` without the replace argument, so it uses Zustand's shallow merge. If the store can acquire keys that are not present in its initial state and the reset must remove them, use `setState(initialState, true)` as in the multi-store example. - A reset restores the initial state; it does not calculate a new baseline from current state. See [Initialize state from props](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/initialize-state-from-props) for initialization patterns. - For nested state, reset from the full initial nested value rather than constructing a partial nested object. See [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) for Zustand's one-level merge behavior. ## Live demos - [Basic reset demo](https://stackblitz.com/edit/zustand-how-to-reset-state-basic) - [Advanced reset demo](https://stackblitz.com/edit/zustand-how-to-reset-state-advanced) ## Related - [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) # Subscribe to a vanilla store outside React Use a vanilla store when browser code outside React needs to read state, update it, or react to changes without rendering a React component. The page below shows two live values; clicking either button updates its value through a selected subscription. ## Create the store and subscribe 1. Install [`zustand`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand): ```bash npm install zustand ``` 2. Put this markup in the page that loads your TypeScript entry point. It gives the example a mounted UI with two controls and two value displays: ```html

X:

Y:

``` 3. In that entry point, create a store with [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore) from [`zustand/vanilla`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-vanilla). Wrap its state creator with [`subscribeWithSelector`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#subscribewithselector) from [`zustand/middleware`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware), then subscribe to each coordinate: ```ts import { subscribeWithSelector } from 'zustand/middleware' import { createStore } from 'zustand/vanilla' type Coordinates = { x: number; y: number } const store = createStore()( subscribeWithSelector(() => ({ x: 0, y: 0 })), ) const xValue = document.getElementById('x-value')! const yValue = document.getElementById('y-value')! const incrementXButton = document.getElementById('increment-x')! const incrementYButton = document.getElementById('increment-y')! const initialState = store.getState() xValue.textContent = String(initialState.x) yValue.textContent = String(initialState.y) store.subscribe( (state) => state.x, (x, previousX) => { xValue.textContent = `${x} (was ${previousX})` }, { fireImmediately: true }, ) store.subscribe( (state) => state.y, (y, previousY) => { yValue.textContent = `${y} (was ${previousY})` }, { fireImmediately: true }, ) incrementXButton.addEventListener('click', () => { store.setState((state) => ({ x: state.x + 1 })) }) incrementYButton.addEventListener('click', () => { store.setState((state) => ({ y: state.y + 1 })) }) ``` ![Both selected values appear as 0, with their initial previous values shown.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/84538c8a7f56ee2e8c2a9b00bf30273b.png) For the vanilla store's API surface, see [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores). Here, subscription callbacks update ordinary DOM outputs directly, without a React component rendering the values. ## Selector subscription options The selector form accepts an optional third argument: | Option | Type | Default | What it does | | --- | --- | --- | --- | | `equalityFn` | `(a: U, b: U) => boolean` | `Object.is` | Compares the current and next selector results; the callback runs when they are not equal according to this function. | | `fireImmediately` | `boolean` | `false` | Calls the callback as soon as it subscribes, passing the current selected value as both the new and previous values. | Use the default comparison for scalar selections such as `state.x`. If you select a newly created object or array, provide an equality function appropriate to that value so equivalent selections do not trigger the callback. ## Pitfalls - For the `subscribe` and middleware API details, see [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react); here, each DOM output tracks only its own coordinate. - When middleware changes the `set` or `get` functions supplied to a state creator, those changes do not carry over to the vanilla store's `setState` and `getState` utilities. Use the store's actions when you need the middleware-wrapped behavior. - For the Next.js shared-store and React Server Component caveat, see [Set up Zustand in Next.js](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/set-up-zustand-in-nextjs). ## Live demo Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see a running Zustand application. ## Related - [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores) - [Selector subscriptions](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/selector-subscriptions) - [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react) # Update Map and Set state When store state contains a `Map` or `Set`, create a new collection instance for each update so a component selecting that collection can detect the change. The example below updates a stock `Map` and toggles a featured-item `Set` in a mounted React component. ## Create actions that replace the collection Use [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) to put the collections and their actions in one store. Each action copies the current collection, changes the copy, and returns it as the updated field. ```tsx import { createRoot } from 'react-dom/client' import { create } from 'zustand' type InventoryStore = { stock: Map featured: Set addTea: () => void toggleFeaturedTea: () => void } const useInventoryStore = create()((set) => ({ stock: new Map([['tea', 2]]), featured: new Set(), addTea: () => set((state) => ({ stock: new Map(state.stock).set('tea', (state.stock.get('tea') ?? 0) + 1), })), toggleFeaturedTea: () => set((state) => { const featured = new Set(state.featured) if (featured.has('tea')) { featured.delete('tea') } else { featured.add('tea') } return { featured } }), })) export default function App() { const stock = useInventoryStore((state) => state.stock) const featured = useInventoryStore((state) => state.featured) const addTea = useInventoryStore((state) => state.addTea) const toggleFeaturedTea = useInventoryStore((state) => state.toggleFeaturedTea) return (

Inventory

Tea in stock: {stock.get('tea') ?? 0}

Featured: {featured.has('tea') ? 'Tea' : 'None'}

) } const container = document.getElementById('root')! createRoot(container).render() ``` Place a `
` in the page and run this as the React entry module. It mounts the component, which initially renders two teas and no featured item. Clicking **Add one tea** replaces the `Map` and increases the displayed count; clicking **Toggle featured tea** replaces the `Set` and switches the displayed value between “Tea” and “None.” ![The inventory shows two teas in stock and no featured item.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/dd52fe98540e14d8990fcc283052df77.png) ## Choose the update for the collection For the immutable-update and shallow-merge rules, see [Immutable state updates](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/immutable-state-updates). These collection-specific operations return the new value for the field you change: | Collection update | New value to return | | --- | --- | | Set one map entry | `new Map(state.items).set(key, value)` | | Delete one map entry | Copy with `new Map(state.items)`, call `delete(key)` on the copy, then return it | | Add one set item | `new Set(state.items).add(item)` | | Delete or toggle a set item | Copy with `new Set(state.items)`, change the copy, then return it | | Clear a collection | Return `new Map()` or `new Set()` for that field | If you initialize an empty collection, provide its element types, as in `new Map()` or `new Set()`. This gives TypeScript the key, value, or item types that later updates use. ## Avoid mutating the stored instance For why an update must produce a changed reference, see [Immutable state updates](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/immutable-state-updates). For other immutable-update pitfalls, see [Update nested state](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) and [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start). ## See it running Open the [Map and Set demo](https://stackblitz.com/edit/vitejs-vite-5cu5ddvx) to see the collection updates in a running app. # Use custom equality functions Use this when the default `Object.is` comparison does not match the rule your selector needs. In React, [`createWithEqualityFn`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-traditional#createwithequalityfn) sets a default comparison for a bound store, while [`useStoreWithEqualityFn`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-traditional#usestorewithequalityfn) applies a comparison to a store selection. ## Before you start Install Zustand and the peer dependency required by the [`zustand/traditional`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-traditional) entry point: ```bash npm install zustand react use-sync-external-store ``` If you only need shallow comparison for an object or array selector, prefer [`useShallow`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-shallow#useshallow). Use a custom equality function when your rule differs from shallow equality. ## Set a store-wide default Pass a generic equality function as the second argument to `createWithEqualityFn`. The mounted component below displays progress from a store; the default comparator considers numeric values equal while they round to the same integer, so the displayed value changes when progress crosses a half-integer threshold. ```tsx import { createWithEqualityFn } from 'zustand/traditional' type ProgressStore = { progress: number advance: () => void } const sameRoundedValue = (previous: T, next: T): boolean => { if (typeof previous === 'number' && typeof next === 'number') { return Math.round(previous) === Math.round(next) } return Object.is(previous, next) } const useProgressStore = createWithEqualityFn()( (set) => ({ progress: 0, advance: () => set((state) => ({ progress: state.progress + 0.2 })), }), sameRoundedValue, ) export default function App() { const progress = useProgressStore((state) => state.progress) const advance = useProgressStore((state) => state.advance) return (

Progress: {progress.toFixed(1)}

) } ``` ![Look at the mounted progress readout and its advance button in the store-wide equality example.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/39b9dc8ec3497f335a6336373d804ce1.png) The custom function is the store hook's default equality function. Because it also receives selections of other types, it uses `Object.is` for non-numeric values; the `advance` action therefore retains its normal reference comparison. ## Set equality for one selection For a store API you already have, pass the store, selector, and equality function to `useStoreWithEqualityFn`. This example uses the API attached to a bound store, then subscribes to its progress value with a per-selection comparison. The page shows tenths for this selection, while the store's own hook continues to use its default comparator. ```tsx import { createWithEqualityFn, useStoreWithEqualityFn } from 'zustand/traditional' type ProgressStore = { progress: number advance: () => void } const useProgressStore = createWithEqualityFn()((set) => ({ progress: 0, advance: () => set((state) => ({ progress: state.progress + 0.2 })), })) export default function App() { const progress = useStoreWithEqualityFn( useProgressStore, (state) => state.progress, (previous, next) => Math.floor(previous * 10) === Math.floor(next * 10), ) const advance = useProgressStore((state) => state.advance) return (

Progress: {progress.toFixed(1)}

) } ``` ![Look at the mounted progress readout and its advance button in the per-selection equality example.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/39b9dc8ec3497f335a6336373d804ce1.png) Here the component's selected value changes in tenths, so each button click updates the displayed value. The equality function compares the selector results, not the complete store state. ## Equality choices | Option | Type | Default | What it does | | --- | --- | --- | --- | | `defaultEqualityFn` in `createWithEqualityFn` | `(a: U, b: U) => boolean` | `Object.is` | Supplies the default comparison used by the bound store's selectors. | | `equalityFn` in `useStoreWithEqualityFn` | `(a: U, b: U) => boolean` | No custom function; the underlying selector comparison uses its default | Decides whether two selected values are equal for this hook call. | Return `true` only when the previous and next selected values are interchangeable for the component. A comparator that reports unequal values as equal prevents the component from seeing those updates. Keep the comparison aligned with what the component renders. ## Pitfalls - `createWithEqualityFn` and `useStoreWithEqualityFn` come from `zustand/traditional`; add `use-sync-external-store` to the application dependencies. - The default comparator applies to each selection made through the bound hook. Use the hook's optional per-selection equality argument when one selector needs a different rule. - Equality does not change the store update. It controls whether the selector's consumer observes the new selection and re-renders. ## Related - [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) for selector patterns and shallow comparison. - [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react) for subscribing to a standalone store in a component. # zustand 🐻 Bear necessities for state management in React ## Install ```bash npm install zustand ``` Install these too only if you use what needs them: `@types/react`, `immer`, `react`, `use-sync-external-store`. ## Functions ### `useStore` ```ts function useStore>( api: S, ): ExtractState function useStore, U>( api: S, selector: (state: ExtractState) => U, ): U ``` ## Constants ### `create` ```ts const create: Create ``` ### `createStore` ```ts const createStore: CreateStore ``` ## Interfaces ### `StoreApi` ```ts interface StoreApi ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `setState` | `SetStateInternal` | | | | `getState` | `() => T` | | | | `getInitialState` | `() => T` | | | | `subscribe` | `(listener: (state: T, prevState: T) => void) => () => void` | | | ### `StoreMutators` Also has every member of `StoreMutators`, listed on its own entry. ```ts interface StoreMutators ``` ## Types ### `ExtractState` ```ts type ExtractState = S extends { getState: () => infer T } ? T : never ``` ### `Mutate` ```ts type Mutate = number extends Ms['length' & keyof Ms] ? S : Ms extends [] ? S : Ms extends [[infer Mi, infer Ma], ...infer Mrs] ? Mutate[Mi & StoreMutatorIdentifier], Mrs> : never ``` ### `StateCreator` ```ts type StateCreator< T, Mis extends [StoreMutatorIdentifier, unknown][] = [], Mos extends [StoreMutatorIdentifier, unknown][] = [], U = T, > = (( setState: Get, Mis>, 'setState', never>, getState: Get, Mis>, 'getState', never>, store: Mutate, Mis>, ) => U) & { $$storeMutators?: Mos } ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `$$storeMutators?` | `Mos` | | | ### `StoreMutatorIdentifier` Also has every member of `String`, listed on its own entry. ```ts type StoreMutatorIdentifier = keyof StoreMutators ``` ### `UseBoundStore` Also has every member of `StoreApi`, listed on its own entry. ```ts type UseBoundStore> = { (): ExtractState (selector: (state: ExtractState) => U): U } & S ``` # zustand/middleware Part of the `zustand` package: install `zustand` and import these from `zustand/middleware`. ## Functions ### `combine` ```ts function combine< T extends object, U extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], >( initialState: T, create: StateCreator, ): StateCreator, Mps, Mcs> ``` ### `createJSONStorage` ```ts function createJSONStorage( getStorage: () => StateStorage, options?: JsonStorageOptions, ): PersistStorage | undefined ``` ### `unstable_ssrSafe` ```ts function ssrSafe< T extends object, U extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], >( config: StateCreator, isSSR: boolean = typeof window === 'undefined', ): StateCreator ``` ## Constants ### `devtools` ```ts const devtools: Devtools ``` ### `persist` ```ts const persist: Persist ``` ### `redux` ```ts const redux: Redux ``` ### `subscribeWithSelector` ```ts const subscribeWithSelector: SubscribeWithSelector ``` ## Interfaces ### `DevtoolsOptions` ```ts interface DevtoolsOptions extends Config ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `name?` | `string` | | | | `enabled?` | `boolean` | | | | `anonymousActionType?` | `string` | | | | `store?` | `string` | | | ### `PersistOptions` ```ts interface PersistOptions< S, PersistedState = S, PersistReturn = unknown, > ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `name` | `string` | | Name of the storage (must be unique) | | `storage?` | `PersistStorage \| undefined` | `createJSONStorage(() => window.localStorage)` | Use a custom persist storage. | | `partialize?` | `(state: S) => PersistedState` | | Filter the persisted value. | | `onRehydrateStorage?` | `( state: S, ) => ((state?: S, error?: unknown) => void) \| void` | | A function returning another (optional) function. The main function will be called before the state rehydration. The returned function will be called after the state rehydration or when an error occurred. | | `version?` | `number` | | If the stored state's version mismatch the one specified here, the storage will not be used. This is useful when adding a breaking change to your store. | | `migrate?` | `( persistedState: unknown, version: number, ) => PersistedState \| Promise` | | A function to perform persisted state migration. This function will be called when persisted state versions mismatch with the one specified here. | | `merge?` | `(persistedState: unknown, currentState: S) => S` | | A function to perform custom hydration merges when combining the stored state with the current one. By default, this function does a shallow merge. | | `skipHydration?` | `boolean` | `false` | An optional boolean that will prevent the persist middleware from triggering hydration on initialization, This allows you to call `rehydrate()` at a specific point in your apps rendering life-cycle. | ### `PersistStorage` ```ts interface PersistStorage ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `getItem` | `( name: string, ) => StorageValue \| null \| Promise \| null>` | | | | `setItem` | `(name: string, value: StorageValue) => R` | | | | `removeItem` | `(name: string) => R` | | | ### `StateStorage` ```ts interface StateStorage ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `getItem` | `(name: string) => string \| null \| Promise` | | | | `setItem` | `(name: string, value: string) => R` | | | | `removeItem` | `(name: string) => R` | | | ## Types ### `NamedSet` ```ts type NamedSet = WithDevtools>['setState'] ``` ### `StorageValue` ```ts type StorageValue = { … } ``` **Properties** | Name | Type | Default | Description | | --- | --- | --- | --- | | `state` | `S` | | | | `version?` | `number` | | | # zustand/shallow Part of the `zustand` package: install `zustand` and import these from `zustand/shallow`. ## Functions ### `shallow` ```ts function shallow(valueA: T, valueB: T): boolean ``` ### `useShallow` ```ts function useShallow(selector: (state: S) => U): (state: S) => U ``` # zustand/react/shallow Part of the `zustand` package: install `zustand` and import these from `zustand/react/shallow`. ## Functions ### `useShallow` ```ts function useShallow(selector: (state: S) => U): (state: S) => U ``` # zustand/traditional Part of the `zustand` package: install `zustand` and import these from `zustand/traditional`. ## Functions ### `useStoreWithEqualityFn` ```ts function useStoreWithEqualityFn>( api: S, ): ExtractState function useStoreWithEqualityFn, U>( api: S, selector: (state: ExtractState) => U, equalityFn?: (a: U, b: U) => boolean, ): U ``` ## Constants ### `createWithEqualityFn` ```ts const createWithEqualityFn: CreateWithEqualityFn ``` ## Types ### `UseBoundStoreWithEqualityFn` Also has every member of `StoreApi`, listed on its own entry. ```ts type UseBoundStoreWithEqualityFn> = { (): ExtractState ( selector: (state: ExtractState) => U, equalityFn?: (a: U, b: U) => boolean, ): U } & S ``` # zustand/vanilla/shallow Part of the `zustand` package: install `zustand` and import these from `zustand/vanilla/shallow`. ## Functions ### `shallow` ```ts function shallow(valueA: T, valueB: T): boolean ``` # zustand/middleware/immer Part of the `zustand` package: install `zustand` and import these from `zustand/middleware/immer`. ## Constants ### `immer` ```ts const immer: Immer ``` # zustand/react Part of the `zustand` package: install `zustand` and import these from `zustand/react`. ## Also exported from here These names are documented with the package that defines them, and can be imported from this one too. - From [zustand](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand): `create`, `UseBoundStore`, `useStore` # zustand/vanilla Part of the `zustand` package: install `zustand` and import these from `zustand/vanilla`. ## Also exported from here These names are documented with the package that defines them, and can be imported from this one too. - From [zustand](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand): `createStore`, `ExtractState`, `Mutate`, `StateCreator`, `StoreApi`, `StoreMutatorIdentifier`, `StoreMutators`