A small, fast and scalable bearbones state-management solution using simplified flux principles. Has a comfy API based on hooks, isn't boilerplatey or opinionated.
Don't disregard it because it's cute. It has quite the claws, lots of time was spent dealing with common pitfalls, like the dreaded [zombie child problem](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children), [react concurrency](https://github.com/bvaughn/rfcs/blob/useMutableSource/text/0000-use-mutable-source.md), and [context loss](https://github.com/facebook/react/issues/13332) between mixed renderers. It may be the one state-manager in the React space that gets all of these right.
You can try a live [demo](https://zustand-demo.pmnd.rs/) and read the [docs](https://zustand.docs.pmnd.rs/).
```bash
npm install zustand
```
:warning: This readme is written for JavaScript users. If you are a TypeScript user, be sure to check out our [TypeScript Usage section](#typescript-usage).
## First create a store
Your store is a hook! You can put anything in it: primitives, objects, functions. State has to be updated immutably and the `set` function [merges state](./docs/guides/immutable-state-and-merging.md) to help it.
```jsx
import { create } from 'zustand'
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
```
## Then bind your components, and that's it!
Use the hook anywhere, no providers are needed. Select your state and the component will re-render on changes.
```jsx
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return
{bears} around here ...
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return
}
```
### Why zustand over redux?
- Simple and un-opinionated
- Makes hooks the primary means of consuming state
- Doesn't wrap your app in context providers
- [Can inform components transiently (without causing render)](#transient-updates-for-often-occurring-state-changes)
### Why zustand over context?
- Less boilerplate
- Renders components only on changes
- Centralized, action-based state management
---
# Recipes
## Fetching everything
You can, but bear in mind that it will cause the component to update on every state change!
```jsx
const state = useBearStore()
```
## Selecting multiple state slices
It detects changes with strict-equality (old === new) by default, this is efficient for atomic state picks.
```jsx
const nuts = useBearStore((state) => state.nuts)
const honey = useBearStore((state) => state.honey)
```
If you want to construct a single object with multiple state-picks inside, similar to redux's mapStateToProps, you can use [useShallow](./docs/guides/prevent-rerenders-with-use-shallow.md) to prevent unnecessary rerenders when the selector output does not change according to shallow equal.
```jsx
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
const useBearStore = create((set) => ({
nuts: 0,
honey: 0,
treats: {},
// ...
}))
// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts, honey } = useBearStore(
useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),
)
// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(
useShallow((state) => [state.nuts, state.honey]),
)
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore(useShallow((state) => Object.keys(state.treats)))
```
For more control over re-rendering, you may provide any custom equality function (this example requires the use of [`createWithEqualityFn`](./docs/migrations/migrating-to-v5.md#using-custom-equality-functions-such-as-shallow)).
```jsx
const treats = useBearStore(
(state) => state.treats,
(oldTreats, newTreats) => compare(oldTreats, newTreats),
)
```
## Overwriting state
The `set` function has a second argument, `false` by default. Instead of merging, it will replace the state model. Be careful not to wipe out parts you rely on, like actions.
```jsx
const useFishStore = create((set) => ({
salmon: 1,
tuna: 2,
deleteEverything: () => set({}, true), // clears the entire store, actions included
deleteTuna: () => set(({ tuna, ...rest }) => rest, true),
}))
```
## Async actions
Just call `set` when you're ready, zustand doesn't care if your actions are async or not.
```jsx
const useFishStore = create((set) => ({
fishies: {},
fetch: async (pond) => {
const response = await fetch(pond)
set({ fishies: await response.json() })
},
}))
```
## Read from state in actions
`set` allows fn-updates `set(state => result)`, but you still have access to state outside of it through `get`.
```jsx
const useSoundStore = create((set, get) => ({
sound: 'grunt',
action: () => {
const sound = get().sound
...
```
## Reading/writing state and reacting to changes outside of components
Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, the resulting hook has utility functions attached to its prototype.
:warning: This technique is not recommended for adding state in [React Server Components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) (typically in Next.js 13 and above). It can lead to unexpected bugs and privacy issues for your users. For more details, see [#2200](https://github.com/pmndrs/zustand/discussions/2200).
```jsx
const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))
// Getting non-reactive fresh state
const paw = useDogStore.getState().paw
// Listening to all changes, fires synchronously on every change
const unsub1 = useDogStore.subscribe(console.log)
// Updating state, will trigger listeners
useDogStore.setState({ paw: false })
// Unsubscribe listeners
unsub1()
// You can of course use the hook as you always would
function Component() {
const paw = useDogStore((state) => state.paw)
...
```
### Using subscribe with selector
If you need to subscribe with a selector,
`subscribeWithSelector` middleware will help.
With this middleware `subscribe` accepts an additional signature:
```ts
subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
```
```js
import { subscribeWithSelector } from 'zustand/middleware'
const useDogStore = create(
subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })),
)
// Listening to selected changes, in this case when "paw" changes
const unsub2 = useDogStore.subscribe((state) => state.paw, console.log)
// Subscribe also exposes the previous value
const unsub3 = useDogStore.subscribe(
(state) => state.paw,
(paw, previousPaw) => console.log(paw, previousPaw),
)
// Subscribe also supports an optional equality function
const unsub4 = useDogStore.subscribe(
(state) => [state.paw, state.fur],
console.log,
{ equalityFn: shallow },
)
// Subscribe and fire immediately
const unsub5 = useDogStore.subscribe((state) => state.paw, console.log, {
fireImmediately: true,
})
```
## Using zustand without React
Zustand core can be imported and used without the React dependency. The only difference is that the create function does not return a hook, but the API utilities.
```jsx
import { createStore } from 'zustand/vanilla'
const store = createStore((set) => ...)
const { getState, setState, subscribe, getInitialState } = store
export default store
```
You can use a vanilla store with `useStore` hook available since v4.
```jsx
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = (selector) => useStore(vanillaStore, selector)
```
:warning: Note that middlewares that modify `set` or `get` are not applied to `getState` and `setState`.
## Transient updates (for often occurring state-changes)
The subscribe function allows components to bind to a state-portion without forcing re-render on changes. Best combine it with useEffect for automatic unsubscribe on unmount. This can make a [drastic](https://codesandbox.io/s/peaceful-johnson-txtws) performance impact when you are allowed to mutate the view directly.
```jsx
const useScratchStore = create((set) => ({ scratches: 0, ... }))
const Component = () => {
// Fetch initial state
const scratchRef = useRef(useScratchStore.getState().scratches)
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
useEffect(() => useScratchStore.subscribe(
state => (scratchRef.current = state.scratches)
), [])
...
```
## Sick of reducers and changing nested states? Use Immer!
Reducing nested structures is tiresome. Have you tried [immer](https://github.com/mweststrate/immer)?
```jsx
import { produce } from 'immer'
const useLushStore = create((set) => ({
lush: { forest: { contains: { a: 'bear' } } },
clearForest: () =>
set(
produce((state) => {
state.lush.forest.contains = null
}),
),
}))
const clearForest = useLushStore((state) => state.clearForest)
clearForest()
```
[Alternatively, there are some other solutions.](./docs/guides/updating-state.md#with-immer)
## Persist middleware
You can persist your store's data using any kind of storage.
```jsx
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
const useFishStore = create(
persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
{
name: 'food-storage', // name of the item in the storage (must be unique)
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
},
),
)
```
[See the full documentation for this middleware.](./docs/reference/integrations/persisting-store-data.md)
## Immer middleware
Immer is available as middleware too.
```jsx
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
const useBeeStore = create(
immer((set) => ({
bees: 0,
addBees: (by) =>
set((state) => {
state.bees += by
}),
})),
)
```
## Can't live without redux-like reducers and action types?
```jsx
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
const reducer = (state, { type, by = 1 }) => {
switch (type) {
case types.increase:
return { grumpiness: state.grumpiness + by }
case types.decrease:
return { grumpiness: state.grumpiness - by }
}
}
const useGrumpyStore = create((set) => ({
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}))
const dispatch = useGrumpyStore((state) => state.dispatch)
dispatch({ type: types.increase, by: 2 })
```
Or, just use our redux-middleware. It wires up your main-reducer, sets the initial state, and adds a dispatch function to the state itself and the vanilla API.
```jsx
import { redux } from 'zustand/middleware'
const useGrumpyStore = create(redux(reducer, initialState))
```
## Redux devtools
Install the [Redux DevTools Chrome extension](https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) to use the devtools middleware.
```jsx
import { devtools } from 'zustand/middleware'
// Usage with a plain action store, it will log actions as "setState"
const usePlainStore = create(devtools((set) => ...))
// Usage with a redux store, it will log full action types
const useReduxStore = create(devtools(redux(reducer, initialState)))
```
One redux devtools connection for multiple stores
```jsx
import { devtools } from 'zustand/middleware'
// Usage with a plain action store, it will log actions as "setState"
const usePlainStore1 = create(devtools((set) => ..., { name, store: storeName1 }))
const usePlainStore2 = create(devtools((set) => ..., { name, store: storeName2 }))
// Usage with a redux store, it will log full action types
const useReduxStore1 = create(devtools(redux(reducer, initialState)), { name, store: storeName3 })
const useReduxStore2 = create(devtools(redux(reducer, initialState)), { name, store: storeName4 })
```
Assigning different connection names will separate stores in redux devtools. This also helps group different stores into separate redux devtools connections.
devtools takes the store function as its first argument, optionally you can name the store or configure [serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize) options with a second argument.
Name store: `devtools(..., {name: "MyStore"})`, which will create a separate instance named "MyStore" in the devtools.
Serialize options: `devtools(..., { serialize: { options: true } })`.
#### Logging Actions
devtools will only log actions from each separated store unlike in a typical _combined reducers_ redux store. See an approach to combining stores https://github.com/pmndrs/zustand/issues/163
You can log a specific action type for each `set` function by passing a third parameter:
```jsx
const useBearStore = create(devtools((set) => ({
...
eatFish: () => set(
(prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 }),
undefined,
'bear/eatFish'
),
...
```
You can also log the action's type along with its payload:
```jsx
...
addFishes: (count) => set(
(prev) => ({ fishes: prev.fishes + count }),
undefined,
{ type: 'bear/addFishes', count, }
),
...
```
If an action type is not provided, it is defaulted to "anonymous". You can customize this default value by providing an `anonymousActionType` parameter:
```jsx
devtools(..., { anonymousActionType: 'unknown', ... })
```
If you wish to disable devtools (on production for instance). You can customize this setting by providing the `enabled` parameter:
```jsx
devtools(..., { enabled: false, ... })
```
## React context
The store created with `create` doesn't require context providers. In some cases, you may want to use contexts for dependency injection or if you want to initialize your store with props from a component. Because the normal store is a hook, passing it as a normal context value may violate the rules of hooks.
The recommended method available since v4 is to use the vanilla store.
```jsx
import { createContext, useContext } from 'react'
import { createStore, useStore } from 'zustand'
const store = createStore(...) // vanilla store without hooks
const StoreContext = createContext()
const App = () => (
...
)
const Component = () => {
const store = useContext(StoreContext)
const slice = useStore(store, selector)
...
```
## TypeScript Usage
Basic typescript usage doesn't require anything special except for writing `create()(...)` instead of `create(...)`...
```ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import type {} from '@redux-devtools/extension' // required for devtools typing
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create()(
devtools(
persist(
(set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}),
{
name: 'bear-storage',
},
),
),
)
```
A more detailed TypeScript guide is [here](docs/guides/beginner-typescript.md) and [there](docs/guides/advanced-typescript.md).
## Best practices
- You may wonder how to organize your code for better maintenance: [Splitting the store into separate slices](./docs/learn/guides/slices-pattern.md).
- Recommended usage for this unopinionated library: [Flux inspired practice](./docs/learn/guides/flux-inspired-practice.md).
- [Calling actions outside a React event handler in pre-React 18](./docs/learn/guides/event-handler-in-pre-react-18.md).
- [Testing](./docs/learn/guides/testing.md)
- For more, have a look [in the docs folder](./docs/index.md)
## Third-Party Libraries
Some users may want to extend Zustand's feature set which can be done using third-party libraries made by the community. For information regarding third-party libraries with Zustand, visit [the doc](./docs/reference/integrations/third-party-libraries.md).
## Comparison with other libraries
- [Difference between zustand and other state management libraries for React](https://zustand.docs.pmnd.rs/learn/getting-started/comparison)
================================================
FILE: docs/index.md
================================================
---
pageType: home
hero:
text: Bear necessities for React state
tagline: A tiny, predictable store with hooks-first ergonomics and escape hatches that stay out of your way.
actions:
- theme: brand
text: Introduction
link: ./learn/getting-started/introduction.md
- theme: alt
text: Quick Start
link: ./learn/index.md
features:
- title: Minimal API, fast adoption
details: Create a store with a single hook, subscribe with selectors, and avoid boilerplate or providers.
- title: Safe under React concurrency
details: Built to avoid zombie children and tearing issues while keeping renders predictable.
- title: Works across React and vanilla
details: Share stores between React, React Native, and non-React environments with the same API surface.
- title: Batteries included
details: Opt into devtools, persistence, Redux-style middleware, and Immer without changing your mental model.
- title: TypeScript-first ergonomics
details: Strongly typed helpers and patterns so your state and actions stay inferred.
- title: Small footprint
details: Tiny bundle size with zero config and performance that keeps pace in production.
---
================================================
FILE: docs/learn/getting-started/comparison.md
================================================
---
title: Comparison
description: How Zustand stacks up against similar libraries
nav: 2
---
Zustand is one of many state management libraries for React.
On this page we will discuss Zustand
in comparison to some of these libraries,
including Redux, Valtio, Jotai, and Recoil.
Each library has its own strengths and weaknesses,
and we will compare key differences and similarities between each.
## Redux
### State Model (vs Redux)
Conceptually, Zustand and Redux are quite similar,
both are based on an immutable state model.
However, Redux requires your app to be wrapped
in context providers; Zustand does not.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
```
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
type Actions = {
dispatch: (action: Action) => void
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const useCountStore = create((set) => ({
count: 0,
dispatch: (action: Action) => set((state) => countReducer(state, action)),
}))
```
**Redux**
```ts
import { createStore } from 'redux'
import { useSelector, useDispatch } from 'react-redux'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const countStore = createStore(countReducer)
```
```ts
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
incremented: (state, qty: number) => {
// Redux Toolkit does not mutate the state, it uses the Immer library
// behind scenes, allowing us to have something called "draft state".
state.value += qty
},
decremented: (state, qty: number) => {
state.value -= qty
},
},
})
const countStore = configureStore({ reducer: countSlice.reducer })
```
### Render Optimization (vs Redux)
When it comes to render optimizations within your app,
there are no major differences in approach between Zustand and Redux.
In both libraries it is recommended
that you manually apply render optimizations by using selectors.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const increment = useCountStore((state) => state.increment)
const decrement = useCountStore((state) => state.decrement)
// ...
}
```
**Redux**
```ts
import { createStore } from 'redux'
import { useSelector, useDispatch } from 'react-redux'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const countStore = createStore(countReducer)
const Component = () => {
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
// ...
}
```
```ts
import { useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
incremented: (state, qty: number) => {
// Redux Toolkit does not mutate the state, it uses the Immer library
// behind scenes, allowing us to have something called "draft state".
state.value += qty
},
decremented: (state, qty: number) => {
state.value -= qty
},
},
})
const countStore = configureStore({ reducer: countSlice.reducer })
const useAppSelector: TypedUseSelectorHook =
useSelector
const useAppDispatch: () => typeof countStore.dispatch = useDispatch
const Component = () => {
const count = useAppSelector((state) => state.count.value)
const dispatch = useAppDispatch()
// ...
}
```
## Valtio
### State Model (vs Valtio)
Zustand and Valtio approach state management
in a fundamentally different way.
Zustand is based on the **immutable** state model,
while Valtio is based on the **mutable** state model.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
obj: { count: number }
}
const store = create(() => ({ obj: { count: 0 } }))
store.setState((prev) => ({ obj: { count: prev.obj.count + 1 } }))
```
**Valtio**
```ts
import { proxy } from 'valtio'
const state = proxy({ obj: { count: 0 } })
state.obj.count += 1
```
### Render Optimization (vs Valtio)
The other difference between Zustand and Valtio
is Valtio makes render optimizations through property access.
However, with Zustand, it is recommended that
you manually apply render optimizations by using selectors.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
const useCountStore = create(() => ({
count: 0,
}))
const Component = () => {
const count = useCountStore((state) => state.count)
// ...
}
```
**Valtio**
```ts
import { proxy, useSnapshot } from 'valtio'
const state = proxy({
count: 0,
})
const Component = () => {
const { count } = useSnapshot(state)
// ...
}
```
## Jotai
### State Model (vs Jotai)
There is one major difference between Zustand and Jotai.
Zustand is a single store,
while Jotai consists of primitive atoms that can be composed together.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
updateCount: (
countCallback: (count: State['count']) => State['count'],
) => void
}
const useCountStore = create((set) => ({
count: 0,
updateCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
```
**Jotai**
```ts
import { atom } from 'jotai'
const countAtom = atom(0)
```
### Render Optimization (vs Jotai)
Jotai achieves render optimizations through atom dependency.
However, with Zustand it is recommended that
you manually apply render optimizations by using selectors.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
updateCount: (
countCallback: (count: State['count']) => State['count'],
) => void
}
const useCountStore = create((set) => ({
count: 0,
updateCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const updateCount = useCountStore((state) => state.updateCount)
// ...
}
```
**Jotai**
```ts
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const Component = () => {
const [count, updateCount] = useAtom(countAtom)
// ...
}
```
## Recoil
### State Model (vs Recoil)
The difference between Zustand and Recoil
is similar to that between Zustand and Jotai.
Recoil depends on atom string keys
instead of atom object referential identities.
Additionally, Recoil needs to wrap your app in a context provider.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
setCount: (countCallback: (count: State['count']) => State['count']) => void
}
const useCountStore = create((set) => ({
count: 0,
setCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
```
**Recoil**
```ts
import { atom } from 'recoil'
const count = atom({
key: 'count',
default: 0,
})
```
### Render Optimization (vs Recoil)
Similar to previous optimization comparisons,
Recoil makes render optimizations through atom dependency.
Whereas with Zustand, it is recommended that
you manually apply render optimizations by using selectors.
**Zustand**
```ts
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
setCount: (countCallback: (count: State['count']) => State['count']) => void
}
const useCountStore = create((set) => ({
count: 0,
setCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const setCount = useCountStore((state) => state.setCount)
// ...
}
```
**Recoil**
```ts
import { atom, useRecoilState } from 'recoil'
const countAtom = atom({
key: 'count',
default: 0,
})
const Component = () => {
const [count, setCount] = useRecoilState(countAtom)
// ...
}
```
## Npm Downloads Trend
- [Npm Downloads Trend of State Management Libraries for React](https://npm-stat.com/charts.html?package=zustand&package=jotai&package=valtio&package=%40reduxjs%2Ftoolkit&package=recoil)
================================================
FILE: docs/learn/getting-started/introduction.md
================================================
---
title: Introduction
description: How to use Zustand
nav: 1
---
A small, fast, and scalable bearbones state management solution.
Zustand has a comfy API based on hooks.
It isn't boilerplatey or opinionated,
but has enough convention to be explicit and flux-like.
Don't disregard it because it's cute, it has claws!
Lots of time was spent to deal with common pitfalls,
like the dreaded [zombie child problem],
[React concurrency], and [context loss]
between mixed renderers.
It may be the one state manager in the React space that gets all of these right.
You can try a live demo [here](https://codesandbox.io/s/dazzling-moon-itop4).
[zombie child problem]: https://react-redux.js.org/api/hooks#stale-props-and-zombie-children
[react concurrency]: https://github.com/bvaughn/rfcs/blob/useMutableSource/text/0000-use-mutable-source.md
[context loss]: https://github.com/facebook/react/issues/13332
## Installation
Zustand is available as a package on NPM for use:
```bash
# NPM
npm install zustand
# Or, use any package manager of your choice.
```
## First create a store
Your store is a hook!
You can put anything in it: primitives, objects, functions.
The `set` function _merges_ state.
```js
import { create } from 'zustand'
const useBear = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))
```
## Then bind your components, and that's it!
You can use the hook anywhere, without the need of providers.
Select your state and the consuming component
will re-render when that state changes.
```jsx
function BearCounter() {
const bears = useBear((state) => state.bears)
return
{bears} bears around here...
}
function Controls() {
const increasePopulation = useBear((state) => state.increasePopulation)
return
}
```
================================================
FILE: docs/learn/guides/advanced-typescript.md
================================================
---
title: Advanced TypeScript Guide
nav: 13
---
## Basic usage
The difference when using TypeScript is that instead of writing `create(...)`, you have to write `create()(...)` (notice the extra parentheses `()` too along with the type parameter) where `T` is the type of the state to annotate it. For example:
```ts
import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
```
Why can't we simply infer the type from the initial state?
**TLDR**: Because state generic `T` is invariant.
Consider this minimal version `create`:
```ts
declare const create: (f: (get: () => T) => T) => T
const x = create((get) => ({
foo: 0,
bar: () => get(),
}))
// `x` is inferred as `unknown` instead of
// interface X {
// foo: number,
// bar: () => X
// }
```
Here, if you look at the type of `f` in `create`, i.e. `(get: () => T) => T`, it "gives" `T` via return (making it covariant), but it also "takes" `T` via `get` (making it contravariant). "So where does `T` come from?" TypeScript wonders. It's like that chicken or egg problem. At the end TypeScript, gives up and infers `T` as `unknown`.
So, as long as the generic to be inferred is invariant (i.e. both covariant and contravariant), TypeScript will be unable to infer it. Another simple example would be this:
```ts
const createFoo = {} as (f: (t: T) => T) => T
const x = createFoo((_) => 'hello')
```
Here again, `x` is `unknown` instead of `string`.
More about the inference (just for the people curious and interested in TypeScript)
In some sense this inference failure is not a problem because a value of type `(f: (t: T) => T) => T` cannot be written. That is to say you can't write the real runtime implementation of `createFoo`. Let's try it:
```js
const createFoo = (f) => f(/* ? */)
```
`createFoo` needs to return the return value of `f`. And to do that we first have to call `f`. And to call it we have to pass a value of type `T`. And to pass a value of type `T` we first have to produce it. But how can we produce a value of type `T` when we don't even know what `T` is? The only way to produce a value of type `T` is to call `f`, but then to call `f` itself we need a value of type `T`. So you see it's impossible to actually write `createFoo`.
So what we're saying is, the inference failure in case of `createFoo` is not really a problem because it's impossible to implement `createFoo`. But what about the inference failure in case of `create`? That also is not really a problem because it's impossible to implement `create` too. Wait a minute, if it's impossible to implement `create` then how does Zustand implement it? The answer is, it doesn't.
Zustand lies that it implemented `create`'s type, it implemented only the most part of it. Here's a simple proof by showing unsoundness. Consider the following code:
```ts
import { create } from 'zustand'
const useBoundStore = create<{ foo: number }>()((_, get) => ({
foo: get().foo,
}))
```
This code compiles. But if we run it, we'll get an exception: "Uncaught TypeError: Cannot read properties of undefined (reading 'foo')". This is because `get` would return `undefined` before the initial state is created (hence you shouldn't call `get` when creating the initial state). The types promise that `get` will never return `undefined` but it does initially, which means Zustand failed to implement it.
And of course Zustand failed because it's impossible to implement `create` the way types promise (in the same way it's impossible to implement `createFoo`). In other words we don't have a type to express the actual `create` we have implemented. We can't type `get` as `() => T | undefined` because it would cause inconvenience and it still won't be correct as `get` is indeed `() => T` eventually, just if called synchronously it would be `() => undefined`. What we need is some kind of TypeScript feature that allows us to type `get` as `(() => T) & WhenSync<() => undefined>`, which of course is extremely far-fetched.
So we have two problems: lack of inference and unsoundness. Lack of inference can be solved if TypeScript can improve its inference for invariants. And unsoundness can be solved if TypeScript introduces something like `WhenSync`. To work around lack of inference we manually annotate the state type. And we can't work around unsoundness, but it's not a big deal because it's not much, calling `get` synchronously anyway doesn't make sense.
Why the currying `()(...)`?
**TLDR**: It is a workaround for [microsoft/TypeScript#10571](https://github.com/microsoft/TypeScript/issues/10571).
Imagine you have a scenario like this:
```ts
declare const withError: (
p: Promise,
) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
declare const doSomething: () => Promise
const main = async () => {
let [error, value] = await withError(doSomething())
}
```
Here, `T` is inferred to be a `string` and `E` is inferred to be `unknown`. You might want to annotate `E` as `Foo`, because you are certain of the shape of error `doSomething()` would throw. However, you can't do that. You can either pass all generics or none. Along with annotating `E` as `Foo`, you will also have to annotate `T` as `string` even though it gets inferred anyway. The solution is to make a curried version of `withError` that does nothing at runtime. Its purpose is to just allow you annotate `E`.
```ts
declare const withError: {
(): (
p: Promise,
) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
(
p: Promise,
): Promise<[error: undefined, value: T] | [error: E, value: undefined]>
}
declare const doSomething: () => Promise
interface Foo {
bar: string
}
const main = async () => {
let [error, value] = await withError()(doSomething())
}
```
This way, `T` gets inferred and you get to annotate `E`. Zustand has the same use case when we want to annotate the state (the first type parameter) but allow other parameters to get inferred.
Alternatively, you can also use `combine`, which infers the state so that you do not need to type it.
```ts
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
const useBearStore = create(
combine({ bears: 0 }, (set) => ({
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
})),
)
```
Be a little careful
We achieve the inference by lying a little in the types of `set`, `get`, and `store` that you receive as parameters. The lie is that they're typed as if the state is the first parameter, when in fact the state is the shallow-merge (`{ ...a, ...b }`) of both first parameter and the second parameter's return. For example, `get` from the second parameter has type `() => { bears: number }` and that is a lie as it should be `() => { bears: number, increase: (by: number) => void }`. And `useBearStore` still has the correct type; for example, `useBearStore.getState` is typed as `() => { bears: number, increase: (by: number) => void }`.
It isn't really a lie because `{ bears: number }` is still a subtype of `{ bears: number, increase: (by: number) => void }`. Therefore, there will be no problem in most cases. You should just be careful while using replace. For example, `set({ bears: 0 }, true)` would compile but will be unsound as it will delete the `increase` function. Another instance where you should be careful is if you use `Object.keys`. `Object.keys(get())` will return `["bears", "increase"]` and not `["bears"]`. The return type of `get` can make you fall for these mistakes.
`combine` trades off a little type-safety for the convenience of not having to write a type for state. Hence, you should use `combine` accordingly. It is fine in most cases and you can use it conveniently.
Note that we don't use the curried version when using `combine` because `combine` "creates" the state. When using a middleware that creates the state, it isn't necessary to use the curried version because the state now can be inferred. Another middleware that creates state is `redux`. So when using `combine`, `redux`, or any other custom middleware that creates the state, we don't recommend using the curried version.
If you want to infer state type also outside of state declaration, you can use the `ExtractState` type helper:
```ts
import { create, ExtractState } from 'zustand'
import { combine } from 'zustand/middleware'
type BearState = ExtractState
const useBearStore = create(
combine({ bears: 0 }, (set) => ({
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
})),
)
```
## Using middlewares
You do not have to do anything special to use middlewares in TypeScript.
```ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create()(
devtools(
persist(
(set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}),
{ name: 'bearStore' },
),
),
)
```
Just make sure you are using them immediately inside `create` so as to make the contextual inference work. Doing something even remotely fancy like the following `myMiddlewares` would require more advanced types.
```ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const myMiddlewares = (f) => devtools(persist(f, { name: 'bearStore' }))
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create()(
myMiddlewares((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
})),
)
```
Also, we recommend using `devtools` middleware as last as possible. For example, when you use it with `immer` as a middleware, it should be `devtools(immer(...))` and not `immer(devtools(...))`. This is because`devtools` mutates the `setState` and adds a type parameter on it, which could get lost if other middlewares (like `immer`) also mutate `setState` before `devtools`. Hence using `devtools` at the end makes sure that no middlewares mutate `setState` before it.
## Authoring middlewares and advanced usage
Imagine you had to write this hypothetical middleware.
```ts
import { create } from 'zustand'
const foo = (f, bar) => (set, get, store) => {
store.foo = bar
return f(set, get, store)
}
const useBearStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useBearStore.foo.toUpperCase())
```
Zustand middlewares can mutate the store. But how could we possibly encode the mutation on the type-level? That is to say how could we type `foo` so that this code compiles?
For a usual statically typed language, this is impossible. But thanks to TypeScript, Zustand has something called a "higher-kinded mutator" that makes this possible. If you are dealing with complex type problems, like typing a middleware or using the `StateCreator` type, you will have to understand this implementation detail. For this, you can [check out #710](https://github.com/pmndrs/zustand/issues/710).
If you are eager to know what the answer is to this particular problem then you can [see it here](#middleware-that-changes-the-store-type).
### Handling Dynamic `replace` Flag
If the value of the `replace` flag is not known at compile time and is determined dynamically, you might face issues. To handle this, you can use a workaround by annotating the `replace` parameter with the parameters of the `setState` function:
```ts
const replaceFlag = Math.random() > 0.5
const args = [{ bears: 5 }, replaceFlag] as Parameters<
typeof useBearStore.setState
>
store.setState(...args)
```
#### Example with `as Parameters` Workaround
```ts
import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
const replaceFlag = Math.random() > 0.5
const args = [{ bears: 5 }, replaceFlag] as Parameters<
typeof useBearStore.setState
>
useBearStore.setState(...args) // Using the workaround
```
By following this approach, you can ensure that your code handles dynamic `replace` flags without encountering type issues.
## Common recipes
### Middleware that doesn't change the store type
```ts
import { create, StateCreator, StoreMutatorIdentifier } from 'zustand'
type Logger = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = [],
>(
f: StateCreator,
name?: string,
) => StateCreator
type LoggerImpl = (
f: StateCreator,
name?: string,
) => StateCreator
const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => {
set(...(a as Parameters))
console.log(...(name ? [`${name}:`] : []), get())
}
const setState = store.setState
store.setState = (...a) => {
setState(...(a as Parameters))
console.log(...(name ? [`${name}:`] : []), store.getState())
}
return f(loggedSet, get, store)
}
export const logger = loggerImpl as unknown as Logger
// ---
const useBearStore = create()(
logger(
(set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}),
'bear-store',
),
)
```
### Middleware that changes the store type
```ts
import {
create,
StateCreator,
StoreMutatorIdentifier,
Mutate,
StoreApi,
} from 'zustand'
type Foo = <
T,
A,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = [],
>(
f: StateCreator,
bar: A,
) => StateCreator
declare module 'zustand' {
interface StoreMutators {
foo: Write, { foo: A }>
}
}
type FooImpl = (
f: StateCreator,
bar: A,
) => StateCreator
const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {
type T = ReturnType
type A = typeof bar
const store = _store as Mutate, [['foo', A]]>
store.foo = bar
return f(set, get, _store)
}
export const foo = fooImpl as unknown as Foo
type Write = Omit & U
type Cast = T extends U ? T : U
// ---
const useBearStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useBearStore.foo.toUpperCase())
```
### `create` without curried workaround
The recommended way to use `create` is using the curried workaround like so: `create()(...)`. This is because it enables you to infer the store type. But if for some reason you do not want to use the workaround, you can pass the type parameters like the following. Note that in some cases, this acts as an assertion instead of annotation, so we don't recommend it.
```ts
import { create } from "zustand"
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<
BearState,
[
['zustand/persist', BearState],
['zustand/devtools', never]
]
>(devtools(persist((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}), { name: 'bearStore' }))
```
### Slices pattern
```ts
import { create, StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
eatFish: () => void
}
interface FishSlice {
fishes: number
addFish: () => void
}
interface SharedSlice {
addBoth: () => void
getBoth: () => number
}
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
const createFishSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
FishSlice
> = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
const createSharedSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
SharedSlice
> = (set, get) => ({
addBoth: () => {
// you can reuse previous methods
get().addBear()
get().addFish()
// or do them from scratch
// set((state) => ({ bears: state.bears + 1, fishes: state.fishes + 1 })
},
getBoth: () => get().bears + get().fishes,
})
const useBoundStore = create()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
...createSharedSlice(...a),
}))
```
A detailed explanation on the slices pattern can be found [here](./slices-pattern.md).
If you have some middlewares then replace `StateCreator` with `StateCreator`. For example, if you are using `devtools` then it will be `StateCreator`. See the ["Middlewares and their mutators reference"](#middlewares-and-their-mutators-reference) section for a list of all mutators.
### Bounded `useStore` hook for vanilla stores
```ts
import { useStore } from 'zustand'
import { createStore } from 'zustand/vanilla'
interface BearState {
bears: number
increase: (by: number) => void
}
const bearStore = createStore()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
function useBearStore(): BearState
function useBearStore(selector: (state: BearState) => T): T
function useBearStore(selector?: (state: BearState) => T) {
return useStore(bearStore, selector!)
}
```
You can also make an abstract `createBoundedUseStore` function if you need to create bounded `useStore` hooks often and want to DRY things up...
```ts
import { useStore, StoreApi } from 'zustand'
import { createStore } from 'zustand/vanilla'
interface BearState {
bears: number
increase: (by: number) => void
}
const bearStore = createStore()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
const createBoundedUseStore = ((store) => (selector) =>
useStore(store, selector)) as >(
store: S,
) => {
(): ExtractState(selector: (state: ExtractState) => T): T
}
type ExtractState = S extends { getState: () => infer X } ? X : never
const useBearStore = createBoundedUseStore(bearStore)
```
## Middlewares and their mutators reference
- `devtools` — `["zustand/devtools", never]`
- `persist` — `["zustand/persist", YourPersistedState]`
`YourPersistedState` is the type of state you are going to persist, ie the return type of `options.partialize`, if you're not passing `partialize` options the `YourPersistedState` becomes `Partial`. Also [sometimes](https://github.com/pmndrs/zustand/issues/980#issuecomment-1162289836) passing actual `PersistedState` won't work. In those cases, try passing `unknown`.
- `immer` — `["zustand/immer", never]`
- `subscribeWithSelector` — `["zustand/subscribeWithSelector", never]`
- `redux` — `["zustand/redux", YourAction]`
- `combine` — no mutator as `combine` does not mutate the store
================================================
FILE: docs/learn/guides/auto-generating-selectors.md
================================================
---
title: Auto Generating Selectors
nav: 14
---
We recommend using selectors when using either the properties or actions from the store. You can access values from the store like so:
```typescript
const bears = useBearStore((state) => state.bears)
```
However, writing these could be tedious. If that is the case for you, you can auto-generate your selectors.
## Create the following function: `createSelectors`
```typescript
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = >>(
_store: S,
) => {
const store = _store as WithSelectors
store.use = {}
for (const k of Object.keys(store.getState())) {
;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
```
If you have a store like this:
```typescript
interface BearState {
bears: number
increase: (by: number) => void
increment: () => void
}
const useBearStoreBase = create()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
increment: () => set((state) => ({ bears: state.bears + 1 })),
}))
```
Apply that function to your store:
```typescript
const useBearStore = createSelectors(useBearStoreBase)
```
Now the selectors are auto generated and you can access them directly:
```typescript
// get the property
const bears = useBearStore.use.bears()
// get the action
const increment = useBearStore.use.increment()
```
## Vanilla Store
If you are using a vanilla store, use the following `createSelectors` function:
```typescript
import { StoreApi, useStore } from 'zustand'
type WithSelectors = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = >(_store: S) => {
const store = _store as WithSelectors
store.use = {}
for (const k of Object.keys(store.getState())) {
;(store.use as any)[k] = () =>
useStore(_store, (s) => s[k as keyof typeof s])
}
return store
}
```
The usage is the same as a React store. If you have a store like this:
```typescript
import { createStore } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
increment: () => void
}
const store = createStore()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
increment: () => set((state) => ({ bears: state.bears + 1 })),
}))
```
Apply that function to your store:
```typescript
const useBearStore = createSelectors(store)
```
Now the selectors are auto generated and you can access them directly:
```typescript
// get the property
const bears = useBearStore.use.bears()
// get the action
const increment = useBearStore.use.increment()
```
## Live Demo
For a working example of this, see the [Code Sandbox](https://codesandbox.io/s/zustand-auto-generate-selectors-forked-rl8v5e?file=/src/selectors.ts).
## Third-party Libraries
- [auto-zustand-selectors-hook](https://github.com/Albert-Gao/auto-zustand-selectors-hook)
- [react-hooks-global-state](https://github.com/dai-shi/react-hooks-global-state)
- [zustood](https://github.com/udecode/zustood)
- [@davstack/store](https://github.com/DawidWraga/davstack)
================================================
FILE: docs/learn/guides/beginner-typescript.md
================================================
---
title: Beginner TypeScript Guide
nav: 12
---
Zustand is a lightweight state manager, particularly used with React. Zustand avoids reducers, context, and boilerplate.
Paired with TypeScript, you get a strongly typed store-state, actions, and selectors-with autocomplete and compile-time safety.
In this basic guide we’ll cover:
- Creating a typed store (state + actions)
- Using the store in React components with type safety
- Resetting the store safely with types
- Extracting and reusing Store type (for props, tests, and utilities)
- Composing multiple selectors and building derived state (with type inference and without extra re-renders)
- Middlewares with TypeScript support (`combine`, `devtools`, `persist`)
- Async actions with typed API responses
- Working with `createWithEqualityFn` (enhanced `create` store function)
- Structuring and coordinating multiple stores
### Creating a Store with State & Actions
Here we describe state and actions using an Typescript interface. The `` generic forces the store to match this shape.
This means if you forget a field or use the wrong type, TypeScript will complain. Unlike plain JS, this guarantees type-safe state management.
The `create` function uses the curried form, which results in a store of type `UseBoundStore>`.
```ts
// store.ts
import { create } from 'zustand'
// Define types for state & actions
interface BearState {
bears: number
food: string
feed: (food: string) => void
}
// Create store using the curried form of `create`
export const useBearStore = create()((set) => ({
bears: 2,
food: 'honey',
feed: (food) => set(() => ({ food })),
}))
```
### Using the Store in Components
Inside components, you can read state and call actions. Selectors `(s) => s.bears` subscribe to only what you need.
This reduces re-renders and improves performance. JS can do this too, but with TS your IDE autocompletes state fields.
```tsx
import { useBearStore } from './store'
function BearCounter() {
// Select only 'bears' to avoid unnecessary re-renders
const bears = useBearStore((s) => s.bears)
return
{bears} bears around
}
```
### Resetting the Store
Resetting is useful after logout or “clear session”. We use `typeof initialState` to avoid repeating property types.
TypeScript updates automatically if `initialState` changes. This is safer and cleaner compared to JS.
```tsx
import { create } from 'zustand'
const initialState = { bears: 0, food: 'honey' }
// Reuse state type dynamically
type BearState = typeof initialState & {
increase: (by: number) => void
reset: () => void
}
const useBearStore = create()((set) => ({
...initialState,
increase: (by) => set((s) => ({ bears: s.bears + by })),
reset: () => set(initialState),
}))
function ResetZoo() {
const { bears, increase, reset } = useBearStore()
return (
{bears}
)
}
```
### Extracting Types
Zustand provides a built-in helper called `ExtractState`. This is useful for tests, utility functions, or component props.
It returns the full type of your store’s state and actions without having to manually redefine them. Extracting the Store type:
```ts
// store.ts
import { create, type ExtractState } from 'zustand'
export const useBearStore = create((set) => ({
bears: 3,
food: 'honey',
increase: (by: number) => set((s) => ({ bears: s.bears + by })),
}))
// Extract the type of the whole store state
export type BearState = ExtractState
```
Using extracted type in tests:
```ts
// test.cy.ts
import { BearState } from './store.ts'
test('should reset store', () => {
const snapshot: BearState = useBearStore.getState()
expect(snapshot.bears).toBeGreaterThanOrEqual(0)
})
```
and in utility function:
```ts
// util.ts
import { BearState } from './store.ts'
function logBearState(state: BearState) {
console.log(`We have ${state.bears} bears eating ${state.food}`)
}
logBearState(useBearStore.getState())
```
### Selectors
#### Multiple Selectors
Sometimes you need more than one property. Returning an object from the selector lets you access multiple fields at once.
However, directly destructuring properties from that object can cause unnecessary re-renders.
To avoid this, it’s recommended to wrap the selector with `useShallow`, which prevents re-renders when the selected values remain shallowly equal.
This is more efficient than subscribing to the whole store. TypeScript ensures you can’t accidentally misspell `bears` or `food`.
See the [API documentation](../../reference/hooks/use-shallow.md) for more details on `useShallow`.
```tsx
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
// Bear store with explicit types
interface BearState {
bears: number
food: number
}
const useBearStore = create()(() => ({
bears: 2,
food: 10,
}))
// In components, you can use both stores safely
function MultipleSelectors() {
const { bears, food } = useBearStore(
useShallow((state) => ({ bears: state.bears, food: state.food })),
)
return (
We have {food} units of food for {bears} bears
)
}
```
#### Derived State with Selectors
Not all values need to be stored directly - some can be computed from existing state. You can derive values using selectors.
This avoids duplication and keeps the store minimal. TypeScript ensures `bears` is a number, so math is safe.
```tsx
import { create } from 'zustand'
interface BearState {
bears: number
foodPerBear: number
}
const useBearStore = create()(() => ({
bears: 3,
foodPerBear: 2,
}))
function TotalFood() {
// Derived value: required amount food for all bears
const totalFood = useBearStore((s) => s.bears * s.foodPerBear) // don't need to have extra property `{ totalFood: 6 }` in your Store
return
We need ${totalFood} jars of honey
}
```
### Middlewares
#### `combine` middleware
This middleware separates initial state and actions, making the code cleaner.
TS automatically infers types from the state and actions, no interface needed.
This is different from JS, where type safety is missing. It’s a very popular style in TypeScript projects.
See the [API documentation](../../reference/middlewares/combine.md) for more details.
```ts
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
interface BearState {
bears: number
increase: () => void
}
// State + actions are separated
export const useBearStore = create()(
combine({ bears: 0 }, (set) => ({
increase: () => set((s) => ({ bears: s.bears + 1 })),
})),
)
```
#### `devtools` middleware
This middleware connects Zustand to Redux DevTools. You can inspect changes, time-travel, and debug state.
It’s extremely useful in development. TS ensures your actions and state remain type-checked even here.
See the [API documentation](../../reference/middlewares/devtools.md) for more details.
```ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface BearState {
bears: number
increase: () => void
}
export const useBearStore = create()(
devtools((set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
})),
)
```
#### `persist` middleware
This middleware keeps your store in `localStorage` (or another storage). This means your bears survive a page refresh.
Great for apps where persistence matters. In TS, the state type stays consistent, so no runtime surprises.
See the [API documentation](../../reference/middlewares/persist.md) for more details.
```ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface BearState {
bears: number
increase: () => void
}
export const useBearStore = create()(
persist(
(set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
}),
{ name: 'bear-storage' }, // localStorage key
),
)
```
### Async Actions
Actions can be async to fetch remote data. Here we fetch bears count and update state.
TS enforces correct API response type (`BearData`). In JS you might misspell `count` - TS prevents that.
```ts
import { create } from 'zustand'
interface BearData {
count: number
}
interface BearState {
bears: number
fetchBears: () => Promise
}
export const useBearStore = create()((set) => ({
bears: 0,
fetchBears: async () => {
const res = await fetch('/api/bears')
const data: BearData = await res.json()
set({ bears: data.count })
},
}))
```
### `createWithEqualityFn`
Variant of `create` with equality built-in. Useful if you always want custom equality checks.
Not common, but shows Zustand’s flexibility. TS still keeps full type inference.
See the [API documentation](../../reference/apis/create-with-equality-fn.md) for more details.
```ts
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
const useBearStore = createWithEqualityFn(() => ({
bears: 0,
}))
const bears = useBearStore((s) => s.bears, Object.is)
// or
const bears = useBearStore((s) => ({ bears: s.bears }), shallow)
```
### Multiple Stores
You can create more than one store for different domains. For example, `BearStore` manages bears and `FishStore` manages fish.
This keeps state isolated and easier to maintain in larger apps. With TypeScript, each store has its own strict type - you can’t accidentally mix bears and fish.
```tsx
import { create } from 'zustand'
// Bear store with explicit types
interface BearState {
bears: number
addBear: () => void
}
const useBearStore = create()((set) => ({
bears: 2,
addBear: () => set((s) => ({ bears: s.bears + 1 })),
}))
// Fish store with explicit types
interface FishState {
fish: number
addFish: () => void
}
const useFishStore = create()((set) => ({
fish: 5,
addFish: () => set((s) => ({ fish: s.fish + 1 })),
}))
// In components, you can use both stores safely
function Zoo() {
const { bears, addBear } = useBearStore()
const { fish, addFish } = useFishStore()
return (
{bears} bears and {fish} fish
)
}
```
### Conclusion
Zustand together with TypeScript provides a balance: you keep the simplicity of small, minimalistic stores, while gaining the safety of strong typing.
You don’t need boilerplate or complex patterns - state and actions live side by side, fully typed, and ready to use.
Start with a basic store to learn the pattern, then expand gradually: use `combine` for cleaner inference, `persist` for storage, and `devtools` for debugging.
================================================
FILE: docs/learn/guides/connect-to-state-with-url-hash.md
================================================
---
title: Connect to state with URL
nav: 10
---
## Connect State with URL Hash
If you want to connect state of a store to URL hash, you can create your own hash storage.
```ts
import { create } from 'zustand'
import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
const hashStorage: StateStorage = {
getItem: (key): string => {
const searchParams = new URLSearchParams(location.hash.slice(1))
const storedValue = searchParams.get(key) ?? ''
return JSON.parse(storedValue)
},
setItem: (key, newValue): void => {
const searchParams = new URLSearchParams(location.hash.slice(1))
searchParams.set(key, JSON.stringify(newValue))
location.hash = searchParams.toString()
},
removeItem: (key): void => {
const searchParams = new URLSearchParams(location.hash.slice(1))
searchParams.delete(key)
location.hash = searchParams.toString()
},
}
export const useBoundStore = create()(
persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
{
name: 'food-storage', // unique name
storage: createJSONStorage(() => hashStorage),
},
),
)
```
## Persist and Connect State with URL Parameters (Example: URL Query Parameters)
There are times when you want to conditionally connect the state to the URL.
This example depicts usage of the URL query parameters
while keeping it synced with another persistence implementation, like `localstorage`.
If you want the URL params to always populate, the conditional check on `getUrlSearch()` can be removed.
The implementation below will update the URL in place, without refresh, as the relevant states change.
```ts
import { create } from 'zustand'
import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
const getUrlSearch = () => {
return window.location.search.slice(1)
}
const persistentStorage: StateStorage = {
getItem: (key): string => {
// Check URL first
if (getUrlSearch()) {
const searchParams = new URLSearchParams(getUrlSearch())
const storedValue = searchParams.get(key)
return JSON.parse(storedValue as string)
} else {
// Otherwise, we should load from localstorage or alternative storage
return JSON.parse(localStorage.getItem(key) as string)
}
},
setItem: (key, newValue): void => {
// Check if query params exist at all, can remove check if always want to set URL
if (getUrlSearch()) {
const searchParams = new URLSearchParams(getUrlSearch())
searchParams.set(key, JSON.stringify(newValue))
window.history.replaceState(null, '', `?${searchParams.toString()}`)
}
localStorage.setItem(key, JSON.stringify(newValue))
},
removeItem: (key): void => {
const searchParams = new URLSearchParams(getUrlSearch())
searchParams.delete(key)
window.location.search = searchParams.toString()
},
}
type LocalAndUrlStore = {
typesOfFish: string[]
addTypeOfFish: (fishType: string) => void
numberOfBears: number
setNumberOfBears: (newNumber: number) => void
}
const storageOptions = {
name: 'fishAndBearsStore',
storage: createJSONStorage(() => persistentStorage),
}
const useLocalAndUrlStore = create()(
persist(
(set) => ({
typesOfFish: [],
addTypeOfFish: (fishType) =>
set((state) => ({ typesOfFish: [...state.typesOfFish, fishType] })),
numberOfBears: 0,
setNumberOfBears: (numberOfBears) => set(() => ({ numberOfBears })),
}),
storageOptions,
),
)
export default useLocalAndUrlStore
```
When generating the URL from a component, you can call buildShareableUrl:
```ts
const buildURLSuffix = (params, version = 0) => {
const searchParams = new URLSearchParams()
const zustandStoreParams = {
state: {
typesOfFish: params.typesOfFish,
numberOfBears: params.numberOfBears,
},
version: version, // version is here because that is included with how Zustand sets the state
}
// The URL param key should match the name of the store, as specified as in storageOptions above
searchParams.set('fishAndBearsStore', JSON.stringify(zustandStoreParams))
return searchParams.toString()
}
export const buildShareableUrl = (params, version) => {
return `${window.location.origin}?${buildURLSuffix(params, version)}`
}
```
The generated URL would look like (here without any encoding, for readability):
`https://localhost/search?fishAndBearsStore={"state":{"typesOfFish":["tilapia","salmon"],"numberOfBears":15},"version":0}}`
### Demo
- Hash: https://stackblitz.com/edit/vitejs-vite-9vg24prg
- Query: https://stackblitz.com/edit/vitejs-vite-hyc97ynf
================================================
FILE: docs/learn/guides/event-handler-in-pre-react-18.md
================================================
---
title: Calling actions outside a React event handler in pre React 18
nav: 11
---
Because React handles `setState` synchronously if it's called outside an event handler, updating the state outside an event handler will force react to update the components synchronously. Therefore, there is a risk of encountering the zombie-child effect.
In order to fix this, the action needs to be wrapped in `unstable_batchedUpdates` like so:
```jsx
import { unstable_batchedUpdates } from 'react-dom' // or 'react-native'
const useFishStore = create((set) => ({
fishes: 0,
increaseFishes: () => set((prev) => ({ fishes: prev.fishes + 1 })),
}))
const nonReactCallback = () => {
unstable_batchedUpdates(() => {
useFishStore.getState().increaseFishes()
})
}
```
More details: https://github.com/pmndrs/zustand/issues/302
================================================
FILE: docs/learn/guides/flux-inspired-practice.md
================================================
---
title: Flux inspired practice
nav: 19
---
Although Zustand is an unopinionated library, we do recommend a few patterns.
These are inspired by practices originally found in [Flux](https://github.com/facebookarchive/flux),
and more recently [Redux](https://redux.js.org/understanding/thinking-in-redux/three-principles),
so if you are coming from another library, you should feel right at home.
However, Zustand does differ in some fundamental ways,
so some terminology may not perfectly align to other libraries.
## Recommended patterns
### Single store
Your applications global state should be located in a single Zustand store.
If you have a large application, Zustand supports [splitting the store into slices](./slices-pattern.md).
### Use `set` / `setState` to update the store
Always use `set` (or `setState`) to perform updates to your store.
`set` (and `setState`) ensures the described update is correctly merged and listeners are appropriately notified.
### Colocate store actions
In Zustand, state can be updated without the use of dispatched actions and reducers found in other Flux libraries.
These store actions can be added directly to the store as shown below.
Optionally, by using `setState` they can be [located external to the store](./practice-with-no-store-actions.md)
```js
const useBoundStore = create((set) => ({
storeSliceA: ...,
storeSliceB: ...,
storeSliceC: ...,
updateX: () => set(...),
updateY: () => set(...),
}))
```
## Redux-like patterns
If you can't live without Redux-like reducers, you can define a `dispatch` function on the root level of the store:
```typescript
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
const reducer = (state, { type, by = 1 }) => {
switch (type) {
case types.increase:
return { grumpiness: state.grumpiness + by }
case types.decrease:
return { grumpiness: state.grumpiness - by }
}
}
const useGrumpyStore = create((set) => ({
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}))
const dispatch = useGrumpyStore((state) => state.dispatch)
dispatch({ type: types.increase, by: 2 })
```
You could also use our redux-middleware. It wires up your main reducer, sets initial state, and adds a dispatch function to the state itself and the vanilla api.
```typescript
import { redux } from 'zustand/middleware'
const useReduxStore = create(redux(reducer, initialState))
```
Another way to update the store could be through functions wrapping the state functions. These could also handle side-effects of actions. For example, with HTTP-calls. To use Zustand in a non-reactive way, see [the readme](https://github.com/pmndrs/zustand#readingwriting-state-and-reacting-to-changes-outside-of-components).
================================================
FILE: docs/learn/guides/how-to-reset-state.md
================================================
---
title: How to reset state
nav: 20
---
The following pattern can be used to reset the state to its initial value.
```ts
const useSomeStore = create()((set, get, store) => ({
// your code here
reset: () => {
set(store.getInitialState())
},
}))
```
Resetting multiple stores at once
```ts
import type { StateCreator } from 'zustand'
import { create: actualCreate } from 'zustand'
const storeResetFns = new Set<() => void>()
const resetAllStores = () => {
storeResetFns.forEach((resetFn) => {
resetFn()
})
}
export const create = (() => {
return (stateCreator: StateCreator) => {
const store = actualCreate(stateCreator)
storeResetFns.add(() => {
store.setState(store.getInitialState(), true)
})
return store
}
}) as typeof actualCreate
```
## Demo
- Basic: https://stackblitz.com/edit/zustand-how-to-reset-state-basic
- Advanced: https://stackblitz.com/edit/zustand-how-to-reset-state-advanced
================================================
FILE: docs/learn/guides/immutable-state-and-merging.md
================================================
---
title: Immutable state and merging
nav: 7
---
Like with React's `useState`, we need to update state immutably.
Here's a typical example:
```jsx
import { create } from 'zustand'
const useCountStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}))
```
The `set` function is to update state in the store.
Because the state is immutable, it should have been like this:
```js
set((state) => ({ ...state, count: state.count + 1 }))
```
However, as this is a common pattern, `set` actually merges state, and
we can skip the `...state` part:
```js
set((state) => ({ count: state.count + 1 }))
```
## Nested objects
The `set` function merges state at only one level.
If you have a nested object, you need to merge them explicitly. You will use the spread operator pattern like so:
```jsx
import { create } from 'zustand'
const useCountStore = create((set) => ({
nested: { count: 0 },
inc: () =>
set((state) => ({
nested: { ...state.nested, count: state.nested.count + 1 },
})),
}))
```
For complex use cases, consider using some libraries that help with immutable updates.
You can refer to [Updating nested state object values](./updating-state.md#deeply-nested-object).
## Replace flag
To disable the merging behavior, you can specify a `replace` boolean value for `set` like so:
```js
set((state) => newState, true)
```
================================================
FILE: docs/learn/guides/initialize-state-with-props.md
================================================
---
title: Initialize state with props
nav: 17
---
In cases where [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) is needed, such as when a store should be initialized with props from a component, the recommended approach is to use a vanilla store with React.context.
## Store creator with `createStore`
```ts
import { createStore } from 'zustand'
interface BearProps {
bears: number
}
interface BearState extends BearProps {
addBear: () => void
}
type BearStore = ReturnType
const createBearStore = (initProps?: Partial) => {
const DEFAULT_PROPS: BearProps = {
bears: 0,
}
return createStore()((set) => ({
...DEFAULT_PROPS,
...initProps,
addBear: () => set((state) => ({ bears: ++state.bears })),
}))
}
```
## Creating a context with `React.createContext`
```ts
import { createContext } from 'react'
export const BearContext = createContext(null)
```
## Basic component usage
```tsx
// Provider implementation
import { useState } from 'react'
function App() {
const [store] = useState(() => createBearStore())
return (
)
}
```
```tsx
// Consumer component
import { useContext } from 'react'
import { useStore } from 'zustand'
function BasicConsumer() {
const store = useContext(BearContext)
if (!store) throw new Error('Missing BearContext.Provider in the tree')
const bears = useStore(store, (s) => s.bears)
const addBear = useStore(store, (s) => s.addBear)
return (
<>
{bears} Bears.
>
)
}
```
## Common patterns
### Wrapping the context provider
```tsx
// Provider wrapper
import { useState } from 'react'
type BearProviderProps = React.PropsWithChildren
function BearProvider({ children, ...props }: BearProviderProps) {
const [store] = useState(() => createBearStore(props))
return {children}
}
```
### Extracting context logic into a custom hook
```tsx
// Mimic the hook returned by `create`
import { useContext } from 'react'
import { useStore } from 'zustand'
function useBearContext(selector: (state: BearState) => T): T {
const store = useContext(BearContext)
if (!store) throw new Error('Missing BearContext.Provider in the tree')
return useStore(store, selector)
}
```
```tsx
// Consumer usage of the custom hook
function CommonConsumer() {
const bears = useBearContext((s) => s.bears)
const addBear = useBearContext((s) => s.addBear)
return (
<>
>
)
}
```
### Optionally allow using a custom equality function
```tsx
// Allow custom equality function by using useStoreWithEqualityFn instead of useStore
import { useContext } from 'react'
import { useStoreWithEqualityFn } from 'zustand/traditional'
function useBearContext(
selector: (state: BearState) => T,
equalityFn?: (left: T, right: T) => boolean,
): T {
const store = useContext(BearContext)
if (!store) throw new Error('Missing BearContext.Provider in the tree')
return useStoreWithEqualityFn(store, selector, equalityFn)
}
```
### Complete example
```tsx
// Provider wrapper & custom hook consumer
function App2() {
return (
)
}
```
================================================
FILE: docs/learn/guides/maps-and-sets-usage.md
================================================
---
title: Map and Set Usage
nav: 8
---
# Map and Set in Zustand
Map and Set are mutable data structures. To use them in Zustand, you must create new instances when updating.
## Map
### Reading a Map
```typescript
const foo = useSomeStore((state) => state.foo)
```
### Updating a Map
Always create a new Map instance:
```ts
// Update single entry
set((state) => ({
foo: new Map(state.foo).set(key, value),
}))
// Delete entry
set((state) => {
const next = new Map(state.foo)
next.delete(key)
return { foo: next }
})
// Update multiple entries
set((state) => {
const next = new Map(state.foo)
next.set('key1', 'value1')
next.set('key2', 'value2')
return { foo: next }
})
// Clear
set({ foo: new Map() })
```
## Set
### Reading a Set
```ts
const bar = useSomeStore((state) => state.bar)
```
### Updating a Set
Always create a new Set instance:
```ts
// Add item
set((state) => ({
bar: new Set(state.bar).add(item),
}))
// Delete item
set((state) => {
const next = new Set(state.bar)
next.delete(item)
return { bar: next }
})
// Toggle item
set((state) => {
const next = new Set(state.bar)
next.has(item) ? next.delete(item) : next.add(item)
return { bar: next }
})
// Clear
set({ bar: new Set() })
```
## Why New Instances?
Zustand detects changes by comparing references. Mutating a Map or Set doesn't change its reference:
```ts
// ❌ Wrong - same reference, no re-render
set((state) => {
state.foo.set(key, value)
return { foo: state.foo }
})
// ✅ Correct - new reference, triggers re-render
set((state) => ({
foo: new Map(state.foo).set(key, value),
}))
```
## Pitfall: Type Hints for Empty Collections
Provide type hints when initializing empty Maps and Sets:
```ts
{
ids: new Set([] as string[]),
users: new Map([] as [string, User][])
}
```
Without type hints, TypeScript infers `never[]` which prevents adding items later.
## Demos
Basic: https://stackblitz.com/edit/vitejs-vite-5cu5ddvx
================================================
FILE: docs/learn/guides/nextjs.md
================================================
---
title: Setup with Next.js
nav: 15
---
> [!NOTE]
> We will be updating this guide soon based on our discussion in https://github.com/pmndrs/zustand/discussions/2740.
[Next.js](https://nextjs.org) is a popular server-side rendering framework for React that presents
some unique challenges for using Zustand properly.
Keep in mind that Zustand store is a global
variable (AKA module state) making it optional to use a `Context`.
These challenges include:
- **Per-request store:** A Next.js server can handle multiple requests simultaneously. This means
that the store should be created per request and should not be shared across requests.
- **SSR friendly:** Next.js applications are rendered twice, first on the server
and again on the client. Having different outputs on both the client and the server will result
in "hydration errors." The store will have to be initialized on the server and then
re-initialized on the client with the same data in order to avoid that. Please read more about
that in our [SSR and Hydration](./ssr-and-hydration.md) guide.
- **SPA routing friendly:** Next.js supports a hybrid model for client side routing, which means
that in order to reset a store, we need to initialize it at the component level using a
`Context`.
- **Server caching friendly:** Recent versions of Next.js (specifically applications using the App
Router architecture) support aggressive server caching. Due to our store being a **module state**,
it is completely compatible with this caching.
We have these general recommendations for the appropriate use of Zustand:
- **No global stores** - Because the store should not be shared across requests, it should not be defined
as a global variable. Instead, the store should be created per request.
- **React Server Components should not read from or write to the store** - RSCs cannot use hooks or context. They aren't
meant to be stateful. Having an RSC read from or write values to a global store violates the
architecture of Next.js.
### Creating a store per request
Let's write our store factory function that will create a new store for each
request.
```json
// tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
> **Note:** do not forget to remove all comments from your `tsconfig.json` file.
### Initializing the store
```ts
// 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 })),
}))
}
```
### Providing the store
Let's use the `createCounterStore` in our component and share it using a context provider.
```tsx
// src/providers/counter-store-provider.tsx
'use client'
import { type ReactNode, createContext, useState, useContext } from 'react'
import { useStore } from 'zustand'
import { type CounterStore, createCounterStore } from '@/stores/counter-store'
export type CounterStoreApi = ReturnType
export const CounterStoreContext = createContext(
undefined,
)
export interface CounterStoreProviderProps {
children: ReactNode
}
export const CounterStoreProvider = ({
children,
}: CounterStoreProviderProps) => {
const [store] = useState(() => createCounterStore())
return (
{children}
)
}
export const useCounterStore = (
selector: (store: CounterStore) => T,
): T => {
const counterStoreContext = useContext(CounterStoreContext)
if (!counterStoreContext) {
throw new Error(`useCounterStore must be used within CounterStoreProvider`)
}
return useStore(counterStoreContext, selector)
}
```
> **Note:** In this example, we ensure that this component is re-render-safe by checking the
> value of the reference, so that the store is only created once. This component will only be
> rendered once per request on the server, but might be re-rendered multiple times on the client if
> there are stateful client components located above this component in the tree, or if this component
> also contains other mutable state that causes a re-render.
### Using the store with different architectures
There are two architectures for a Next.js application: the
[Pages Router](https://nextjs.org/docs/pages/building-your-application/routing) and the
[App Router](https://nextjs.org/docs/app/building-your-application/routing). The usage of Zustand on
both architectures should be the same with slight differences related to each architecture.
#### Pages Router
```tsx
// src/components/pages/home-page.tsx
import { useCounterStore } from '@/providers/counter-store-provider'
export const HomePage = () => {
const { count, incrementCount, decrementCount } = useCounterStore(
(state) => state,
)
return (
Count: {count}
)
}
```
```tsx
// src/_app.tsx
import type { AppProps } from 'next/app'
import { CounterStoreProvider } from '@/providers/counter-store-provider'
export default function App({ Component, pageProps }: AppProps) {
return (
)
}
```
```tsx
// src/pages/index.tsx
import { HomePage } from '@/components/pages/home-page'
export default function Home() {
return
}
```
> **Note:** creating a store per route would require creating and sharing the store
> at page (route) component level. Try not to use this if you do not need to create
> a store per route.
```tsx
// src/pages/index.tsx
import { CounterStoreProvider } from '@/providers/counter-store-provider'
import { HomePage } from '@/components/pages/home-page'
export default function Home() {
return (
)
}
```
#### App Router
```tsx
// src/components/pages/home-page.tsx
'use client'
import { useCounterStore } from '@/providers/counter-store-provider'
export const HomePage = () => {
const { count, incrementCount, decrementCount } = useCounterStore(
(state) => state,
)
return (
Count: {count}
)
}
```
```tsx
// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { CounterStoreProvider } from '@/providers/counter-store-provider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
{children}
)
}
```
```tsx
// src/app/page.tsx
import { HomePage } from '@/components/pages/home-page'
export default function Home() {
return
}
```
> **Note:** creating a store per route would require creating and sharing the store
> at page (route) component level. Try not to use this if you do not need to create
> a store per route.
```tsx
// src/app/page.tsx
import { CounterStoreProvider } from '@/providers/counter-store-provider'
import { HomePage } from '@/components/pages/home-page'
export default function Home() {
return (
)
}
```
================================================
FILE: docs/learn/guides/practice-with-no-store-actions.md
================================================
---
title: Practice with no store actions
nav: 5
---
The recommended usage is to colocate actions and states within the store (let your actions be located together with your state).
For example:
```js
export const useBoundStore = create((set) => ({
count: 0,
text: 'hello',
inc: () => set((state) => ({ count: state.count + 1 })),
setText: (text) => set({ text }),
}))
```
This creates a self-contained store with data and actions together.
---
An alternative approach is to define actions at module level, external to the store.
```js
export const useBoundStore = create(() => ({
count: 0,
text: 'hello',
}))
export const inc = () =>
useBoundStore.setState((state) => ({ count: state.count + 1 }))
export const setText = (text) => useBoundStore.setState({ text })
```
This has a few advantages:
- It doesn't require a hook to call an action;
- It facilitates code splitting.
While this pattern doesn't offer any downsides, some may prefer colocating due to its encapsulated nature.
================================================
FILE: docs/learn/guides/prevent-rerenders-with-use-shallow.md
================================================
---
title: Prevent rerenders with useShallow
nav: 9
---
When you need to subscribe to a computed state from a store, the recommended way is to
use a selector.
The computed selector will cause a rerender if the output has changed according to [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is?retiredLocale=it).
In this case you might want to use `useShallow` to avoid a rerender if the computed value is always shallow
equal the previous one.
## Example
We have a store that associates to each bear a meal and we want to render their names.
```js
import { create } from 'zustand'
const useMeals = create(() => ({
papaBear: 'large porridge-pot',
mamaBear: 'middle-size porridge pot',
littleBear: 'A little, small, wee pot',
}))
export const BearNames = () => {
const names = useMeals((state) => Object.keys(state))
return
{names.join(', ')}
}
```
Now papa bear wants a pizza instead:
```js
useMeals.setState({
papaBear: 'a large pizza',
})
```
This change causes `BearNames` rerenders even though the actual output of `names` has not changed according to shallow equal.
We can fix that using `useShallow`!
```js
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',
}))
export const BearNames = () => {
const names = useMeals(useShallow((state) => Object.keys(state)))
return
{names.join(', ')}
}
```
Now they can all order other meals without causing unnecessary rerenders of our `BearNames` component.
================================================
FILE: docs/learn/guides/slices-pattern.md
================================================
---
title: Slices Pattern
nav: 6
---
## Slicing the store into smaller stores
Your store can become bigger and bigger and tougher to maintain as you add more features.
You can divide your main store into smaller individual stores to achieve modularity. This is simple to accomplish in Zustand!
The first individual store:
```js
export const createFishSlice = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
```
Another individual store:
```js
export const createBearSlice = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
```
You can now combine both the stores into **one bounded store**:
```js
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
export const useBoundStore = create((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
```
### Usage in a React component
```jsx
import { useBoundStore } from './stores/useBoundStore'
function App() {
const bears = useBoundStore((state) => state.bears)
const fishes = useBoundStore((state) => state.fishes)
const addBear = useBoundStore((state) => state.addBear)
return (
Number of bears: {bears}
Number of fishes: {fishes}
)
}
export default App
```
### Updating multiple stores
You can update multiple stores, at the same time, in a single function.
```js
export const createBearFishSlice = (set, get) => ({
addBearAndFish: () => {
get().addBear()
get().addFish()
},
})
```
Combining all the stores together is the same as before.
```js
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { createBearFishSlice } from './createBearFishSlice'
export const useBoundStore = create((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
...createBearFishSlice(...a),
}))
```
## Adding middlewares
Adding middlewares to a combined store is the same as with other normal stores.
Adding [`persist` middleware](../../reference/integrations/persisting-store-data.md) to our `useBoundStore`:
```js
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { persist } from 'zustand/middleware'
export const useBoundStore = create(
persist(
(...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}),
{ name: 'bound-store' },
),
)
```
Please keep in mind you should only apply middlewares in the combined store. Applying them inside individual slices can lead to unexpected issues.
## Usage with TypeScript
A detailed guide on how to use the slice pattern in Zustand with TypeScript can be found [here](./advanced-typescript.md#slices-pattern).
================================================
FILE: docs/learn/guides/ssr-and-hydration.md
================================================
---
title: SSR and Hydration
nav: 16
---
## Server-side Rendering (SSR)
Server-side Rendering (SSR) is a technique that helps us render our components into
HTML strings on the server, send them directly to the browser, and finally "hydrate" the
static markup into a fully interactive app on the client.
### React
Let's say we want to render a stateless app using React. In order to do that, we need
to use `express`, `react` and `react-dom/server`. We don't need `react-dom/client`
since it's a stateless app.
Let's dive into that:
- `express` helps us build a web app that we can run using Node,
- `react` helps us build the UI components that we use in our app,
- `react-dom/server` helps us render our components on a server.
```json
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "esnext"
},
"include": ["**/*"]
}
```
> **Note:** do not forget to remove all comments from your `tsconfig.json` file.
```tsx
// app.tsx
export const App = () => {
return (
Static Server-side-rendered App
Hello World!
)
}
```
```tsx
// server.tsx
import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { App } from './app.tsx'
const port = Number.parseInt(process.env.PORT || '3000', 10)
const app = express()
app.get('/', (_, res) => {
const { pipe } = ReactDOMServer.renderToPipeableStream(, {
onShellReady() {
res.setHeader('content-type', 'text/html')
pipe(res)
},
})
})
app.listen(port, () => {
console.log(`Server is listening at ${port}`)
})
```
```sh
tsc --build
```
```sh
node server.js
```
## Hydration
Hydration turns the initial HTML snapshot from the server into a fully interactive app
that runs in the browser. The right way to "hydrate" a component is by using `hydrateRoot`.
### React
Let's say we want to render a stateful app using React. In order to do that we need to
use `express`, `react`, `react-dom/server` and `react-dom/client`.
Let's dive into that:
- `express` helps us build a web app that we can run using Node,
- `react` helps us build the UI components that we use in our app,
- `react-dom/server` helps us render our components on a server,
- `react-dom/client` helps us hydrate our components on a client.
> **Note:** Do not forget that even if we can render our components on a server, it is
> important to "hydrate" them on a client to make them interactive.
```json
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "esnext"
},
"include": ["**/*"]
}
```
> **Note:** do not forget to remove all comments in your `tsconfig.json` file.
```tsx
// app.tsx
export const App = () => {
return (
Static Server-side-rendered App
Hello World!
)
}
```
```tsx
// main.tsx
import ReactDOMClient from 'react-dom/client'
import { App } from './app.tsx'
ReactDOMClient.hydrateRoot(document, )
```
```tsx
// server.tsx
import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { App } from './app.tsx'
const port = Number.parseInt(process.env.PORT || '3000', 10)
const app = express()
app.use('/', (_, res) => {
const { pipe } = ReactDOMServer.renderToPipeableStream(, {
bootstrapScripts: ['/main.js'],
onShellReady() {
res.setHeader('content-type', 'text/html')
pipe(res)
},
})
})
app.listen(port, () => {
console.log(`Server is listening at ${port}`)
})
```
```sh
tsc --build
```
```sh
node server.js
```
> **Warning:** The React tree you pass to `hydrateRoot` needs to produce the same output as it did on the server.
> The most common causes leading to hydration errors include:
>
> - Extra whitespace (like newlines) around the React-generated HTML inside the root node.
> - Using checks like typeof window !== 'undefined' in your rendering logic.
> - Using browser-only APIs like `window.matchMedia` in your rendering logic.
> - Rendering different data on the server and the client.
>
> React recovers from some hydration errors, but you must fix them like other bugs. In the best case, they’ll lead to a slowdown; in the worst case, event handlers can get attached to the wrong elements.
You can read more about the caveats and pitfalls here: [hydrateRoot](https://react.dev/reference/react-dom/client/hydrateRoot)
================================================
FILE: docs/learn/guides/testing.md
================================================
---
title: Testing
description: Writing Tests
nav: 18
---
## Setting Up a Test Environment
### Test Runners
Usually, your test runner needs to be configured to run JavaScript/TypeScript syntax. If you're
going to be testing UI components, you will likely need to configure the test runner to use JSDOM
to provide a mock DOM environment.
See these resources for test runner configuration instructions:
- **Jest**
- [Jest: Getting Started](https://jestjs.io/docs/getting-started)
- [Jest: Configuration - Test Environment](https://jestjs.io/docs/configuration#testenvironment-string)
- **Vitest**
- [Vitest: Getting Started](https://vitest.dev/guide)
- [Vitest: Configuration - Test Environment](https://vitest.dev/config/#environment)
### UI and Network Testing Tools
**We recommend using [React Testing Library (RTL)](https://testing-library.com/docs/react-testing-library/intro)
to test out React components that connect to Zustand**. RTL is a simple and complete React DOM
testing utility that encourages good testing practices. It uses ReactDOM's `render` function and
`act` from `react-dom/tests-utils`. Furthermore, [Native Testing Library (RNTL)](https://testing-library.com/docs/react-native-testing-library/intro)
is the alternative to RTL to test out React Native components. The [Testing Library](https://testing-library.com/)
family of tools also includes adapters for many other popular frameworks.
We also recommend using [Mock Service Worker (MSW)](https://mswjs.io/) to mock network requests, as
this means your application logic does not need to be changed or mocked when writing tests.
- **React Testing Library (DOM)**
- [DOM Testing Library: Setup](https://testing-library.com/docs/dom-testing-library/setup)
- [React Testing Library: Setup](https://testing-library.com/docs/react-testing-library/setup)
- [Testing Library Jest-DOM Matchers](https://testing-library.com/docs/ecosystem-jest-dom)
- **Native Testing Library (React Native)**
- [Native Testing Library: Setup](https://testing-library.com/docs/react-native-testing-library/setup)
- **User Event Testing Library (DOM)**
- [User Event Testing Library: Setup](https://testing-library.com/docs/user-event/setup)
- **TypeScript for Jest**
- [TypeScript for Jest: Setup](https://kulshekhar.github.io/ts-jest/docs/getting-started/installation)
- **TypeScript for Node**
- [TypeScript for Node: Setup](https://typestrong.org/ts-node/docs/installation)
- **Mock Service Worker**
- [MSW: Installation](https://mswjs.io/docs/getting-started/install)
- [MSW: Setting up mock requests](https://mswjs.io/docs/getting-started/mocks/rest-api)
- [MSW: Mock server configuration for Node](https://mswjs.io/docs/getting-started/integrate/node)
## Setting Up Zustand for testing
> **Note**: Since Jest and Vitest have slight differences, like Vitest using **ES modules** and Jest using
> **CommonJS modules**, you need to keep that in mind if you are using Vitest instead of Jest.
The mock provided below will enable the relevant test runner to reset the zustand stores after each test.
### Shared code just for testing purposes
This shared code was added to avoid code duplication in our demo since we use the same counter store
creator for both implementations, with and without `Context` API — `createStore` and `create`, respectively.
```ts
// shared/counter-store-creator.ts
import { type StateCreator } from 'zustand'
export type CounterStore = {
count: number
inc: () => void
}
export const counterStoreCreator: StateCreator = (set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
})
```
### Jest
In the next steps we are going to setup our Jest environment in order to mock Zustand.
```ts
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'
export * from 'zustand'
const { create: actualCreate, createStore: actualCreateStore } =
jest.requireActual('zustand')
// a variable to hold reset functions for all stores declared in the app
export const storeResetFns = new Set<() => void>()
const createUncurried = (
stateCreator: ZustandExportedTypes.StateCreator,
) => {
const store = actualCreate(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}
// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = ((
stateCreator: ZustandExportedTypes.StateCreator,
) => {
console.log('zustand create mock')
// to support curried version of create
return typeof stateCreator === 'function'
? createUncurried(stateCreator)
: createUncurried
}) as typeof ZustandExportedTypes.create
const createStoreUncurried = (
stateCreator: ZustandExportedTypes.StateCreator,
) => {
const store = actualCreateStore(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}
// when creating a store, we get its initial state, create a reset function and add it in the set
export const createStore = ((
stateCreator: ZustandExportedTypes.StateCreator,
) => {
console.log('zustand createStore mock')
// to support curried version of createStore
return typeof stateCreator === 'function'
? createStoreUncurried(stateCreator)
: createStoreUncurried
}) as typeof ZustandExportedTypes.createStore
// reset all stores after each test run
afterEach(() => {
act(() => {
storeResetFns.forEach((resetFn) => {
resetFn()
})
})
})
```
```ts
// setup-jest.ts
import '@testing-library/jest-dom'
```
```ts
// jest.config.ts
import type { JestConfigWithTsJest } from 'ts-jest'
const config: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./setup-jest.ts'],
}
export default config
```
> **Note**: to use TypeScript we need to install two packages `ts-jest` and `ts-node`.
### Vitest
In the next steps we are going to setup our Vitest environment in order to mock Zustand.
> **Warning:** In Vitest you can change the [root](https://vitest.dev/config/#root).
> Due to that, you need make sure that you are creating your `__mocks__` directory in the right place.
> Let's say that you change the **root** to `./src`, that means you need to create a `__mocks__`
> directory under `./src`. The end result would be `./src/__mocks__`, rather than `./__mocks__`.
> Creating `__mocks__` directory in the wrong place can lead to issues when using Vitest.
```ts
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'
export * from 'zustand'
const { create: actualCreate, createStore: actualCreateStore } =
await vi.importActual('zustand')
// a variable to hold reset functions for all stores declared in the app
export const storeResetFns = new Set<() => void>()
const createUncurried = (
stateCreator: ZustandExportedTypes.StateCreator,
) => {
const store = actualCreate(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}
// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = ((
stateCreator: ZustandExportedTypes.StateCreator,
) => {
console.log('zustand create mock')
// to support curried version of create
return typeof stateCreator === 'function'
? createUncurried(stateCreator)
: createUncurried
}) as typeof ZustandExportedTypes.create
const createStoreUncurried = (
stateCreator: ZustandExportedTypes.StateCreator,
) => {
const store = actualCreateStore(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}
// when creating a store, we get its initial state, create a reset function and add it in the set
export const createStore = ((
stateCreator: ZustandExportedTypes.StateCreator,
) => {
console.log('zustand createStore mock')
// to support curried version of createStore
return typeof stateCreator === 'function'
? createStoreUncurried(stateCreator)
: createStoreUncurried
}) as typeof ZustandExportedTypes.createStore
// reset all stores after each test run
afterEach(() => {
act(() => {
storeResetFns.forEach((resetFn) => {
resetFn()
})
})
})
```
> **Note**: without [globals configuration](https://vitest.dev/config/#globals) enabled, we need
> to add `import { afterEach, vi } from 'vitest'` at the top.
```ts
// global.d.ts
///
///
```
> **Note**: without [globals configuration](https://vitest.dev/config/#globals) enabled, we do
> need to remove `/// `.
```ts
// setup-vitest.ts
import '@testing-library/jest-dom/vitest'
vi.mock('zustand') // to make it work like Jest (auto-mocking)
```
> **Note**: without [globals configuration](https://vitest.dev/config/#globals) enabled, we need
> to add `import { vi } from 'vitest'` at the top.
```ts
// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'
export default defineConfig((configEnv) =>
mergeConfig(
viteConfig(configEnv),
defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./setup-vitest.ts'],
},
}),
),
)
```
### Testing Components
In the next examples we are going to use `useCounterStore`
> **Note**: all of these examples are written using TypeScript.
```ts
// shared/counter-store-creator.ts
import { type StateCreator } from 'zustand'
export type CounterStore = {
count: number
inc: () => void
}
export const counterStoreCreator: StateCreator = (set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
})
```
```ts
// stores/use-counter-store.ts
import { create } from 'zustand'
import {
type CounterStore,
counterStoreCreator,
} from '../shared/counter-store-creator'
export const useCounterStore = create()(counterStoreCreator)
```
```tsx
// contexts/use-counter-store-context.tsx
import { type ReactNode, createContext, useContext, useState } from 'react'
import { createStore } from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
import {
type CounterStore,
counterStoreCreator,
} from '../shared/counter-store-creator'
export const createCounterStore = () => {
return createStore(counterStoreCreator)
}
export type CounterStoreApi = ReturnType
export const CounterStoreContext = createContext(
undefined,
)
export interface CounterStoreProviderProps {
children: ReactNode
}
export const CounterStoreProvider = ({
children,
}: CounterStoreProviderProps) => {
const [store] = useState(() => createCounterStore())
return (
{children}
)
}
export type UseCounterStoreContextSelector = (store: CounterStore) => T
export const useCounterStoreContext = (
selector: UseCounterStoreContextSelector,
): T => {
const counterStoreContext = useContext(CounterStoreContext)
if (counterStoreContext === undefined) {
throw new Error(
'useCounterStoreContext must be used within CounterStoreProvider',
)
}
return useStoreWithEqualityFn(counterStoreContext, selector, shallow)
}
```
```tsx
// components/counter/counter.tsx
import { useCounterStore } from '../../stores/use-counter-store'
export function Counter() {
const { count, inc } = useCounterStore()
return (
)
}
export const CounterWithContext = () => {
return (
)
}
```
```tsx
// components/counter-with-context/index.ts
export * from './counter-with-context'
```
```tsx
// components/counter-with-context/counter-with-context.test.tsx
import { act, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CounterStoreContext } from '../../../contexts/use-counter-store-context'
import { counterStoreCreator } from '../../../shared/counter-store-creator'
describe('CounterWithContext', () => {
test('should render with initial state of 1', async () => {
const counterStore = counterStoreCreator()
renderCounterWithContext(counterStore)
expect(counterStore.getState().count).toBe(1)
expect(
await screen.findByRole('button', { name: /one up/i }),
).toBeInTheDocument()
})
test('should increase count by clicking a button', async () => {
const user = userEvent.setup()
const counterStore = counterStoreCreator()
renderCounterWithContext(counterStore)
expect(counterStore.getState().count).toBe(1)
await user.click(await screen.findByRole('button', { name: /one up/i }))
expect(counterStore.getState().count).toBe(2)
})
})
const renderCounterWithContext = (store) => {
return render(, {
wrapper: ({ children }) => (
{children}
),
})
}
```
## References
- **React Testing Library**: [React Testing Library (RTL)](https://testing-library.com/docs/react-testing-library/intro)
is a very lightweight solution for testing React components. It provides utility functions on top
of `react-dom` and `react-dom/test-utils`, in a way that encourages better testing practices. Its
primary guiding principle is: "The more your tests resemble the way your software is used, the
more confidence they can give you."
- **Native Testing Library**: [Native Testing Library (RNTL)](https://testing-library.com/docs/react-native-testing-library/intro)
is a very lightweight solution for testing React Native components, similarly to RTL, but its
functions are built on top of `react-test-renderer`.
- **Testing Implementation Details**: Blog post by Kent C. Dodds on why he recommends to avoid
[testing implementation details](https://kentcdodds.com/blog/testing-implementation-details).
## Demos
- Jest: https://stackblitz.com/edit/jest-zustand
- Vitest: https://stackblitz.com/edit/vitest-zustand
================================================
FILE: docs/learn/guides/tutorial-tic-tac-toe.md
================================================
---
title: 'Tutorial: Tic-Tac-Toe'
description: Building a game
nav: 3
---
# Tutorial: Tic-Tac-Toe
## Building a game
You will build a small tic-tac-toe game during this tutorial. This tutorial does assume existing
React knowledge. The techniques you'll learn in the tutorial are fundamental to building any React
app, and fully understanding it will give you a deep understanding of React and Zustand.
> [!NOTE]
> This tutorial is crafted for those who learn best through hands-on experience and want to swiftly
> create something tangible. It draws inspiration from React's tic-tac-toe tutorial.
The tutorial is divided into several sections:
- Setup for the tutorial will give you a starting point to follow the tutorial.
- Overview will teach you the fundamentals of React: components, props, and state.
- Completing the game will teach you the most common techniques in React development.
- Adding time travel will give you a deeper insight into the unique strengths of React.
### What are you building?
In this tutorial, you'll build an interactive tic-tac-toe game with React and Zustand.
You can see what it will look like when you're finished here:
```jsx
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
const useGameStore = create(
combine(
{
history: [Array(9).fill(null)],
currentMove: 0,
},
(set, get) => {
return {
setHistory: (nextHistory) => {
set((state) => ({
history:
typeof nextHistory === 'function'
? nextHistory(state.history)
: nextHistory,
}))
},
setCurrentMove: (nextCurrentMove) => {
set((state) => ({
currentMove:
typeof nextCurrentMove === 'function'
? nextCurrentMove(state.currentMove)
: nextCurrentMove,
}))
},
}
},
),
)
function Square({ value, onSquareClick }) {
return (
)
}
function Board({ xIsNext, squares, onPlay }) {
const winner = calculateWinner(squares)
const turns = calculateTurns(squares)
const player = xIsNext ? 'X' : 'O'
const status = calculateStatus(winner, turns, player)
function handleClick(i) {
if (squares[i] || winner) return
const nextSquares = squares.slice()
nextSquares[i] = player
onPlay(nextSquares)
}
return (
<>
{history.map((_, historyIndex) => {
const description =
historyIndex > 0
? `Go to move #${historyIndex}`
: 'Go to game start'
return (
)
})}
)
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]
}
}
return null
}
function calculateTurns(squares) {
return squares.filter((square) => !square).length
}
function calculateStatus(winner, turns, player) {
if (!winner && !turns) return 'Draw'
if (winner) return `Winner ${winner}`
return `Next player: ${player}`
}
```
### Building the board
Let's start by creating the `Square` component, which will be a building block for our `Board`
component. This component will represent each square in our game.
The `Square` component should take `value` and `onSquareClick` as props. It should return a
`