Migrating to v4
The only breaking changes are in types. If you are using Zustand with TypeScript or JSDoc type annotations, this guide applies. Otherwise, no migration is required.
Also, it's recommended to first read the new TypeScript Guide, so that the migration is easier to understand.
In addition to this migration guide, you can also check the diff of the test files in the Zustand repository from v3 to v4.
create
Applicable imports
import create from 'zustand'
import create from 'zustand/vanilla'
Change
- create:
- < State
- , StoreSetState = StoreApi<State>["set"]
- , StoreGetState = StoreApi<State>["get"]
- , Store = StoreApi<State>
- >
- (f: ...) => ...
+ create:
+ { <State>(): (f: ...) => ...
+ , <State, Mutators>(f: ...) => ...
+ }
Migration
If you are not passing any type parameters to create
,
no migration is required.
If you are using a "leaf" middleware like combine
or redux
,
remove all type parameters from create
.
Else, replace create<T, ...>(...)
with create<T>()(...)
.
StateCreator
Applicable imports
import type { StateCreator } from 'zustand'
import type { StateCreator } from 'zustand/vanilla'
Change
- type StateCreator
- < State
- , StoreSetState = StoreApi<State>["set"]
- , StoreGetState = StoreApi<State>["get"]
- , Store = StoreApi<State>
- > =
- ...
+ type StateCreator
+ < State
+ , InMutators extends [StoreMutatorIdentifier, unknown][] = []
+ , OutMutators extends [StoreMutatorIdentifier, unknown][] = []
+ , Return = State
+ > =
+ ...
Migration
If you are using StateCreator
,
you are likely authoring a middleware
or using the "slices" pattern.
For that check the
Authoring middlewares and advanced usage
and Common recipes
sections of the TypeScript Guide.
PartialState
Applicable imports
import type { PartialState } from 'zustand'
import type { PartialState } from 'zustand/vanilla'
Change
- type PartialState
- < T extends State
- , K1 extends keyof T = keyof T
- , K2 extends keyof T = K1
- , K3 extends keyof T = K2
- , K4 extends keyof T = K3
- > =
- | (Pick<T, K1> | Pick<T, K2> | Pick<T, K3> | Pick<T, K4> | T)
- | ((state: T) => Pick<T, K1> | Pick<T, K2> | Pick<T, K3> | Pick<T, K4> | T)
+ type PartialState<T> =
+ | Partial<T>
+ | ((state: T) => Partial<T>)
Migration
Replace PartialState<T, ...>
with PartialState<T>
and preferably turn on exactOptionalPropertyTypes
in your tsconfig.json
:
{
"compilerOptions": {
"exactOptionalPropertyTypes": true
}
}
We're no longer using the trick to disallow { foo: undefined }
to be assigned to Partial<{ foo: string }>
.
Instead, we're relying on the users to turn on exactOptionalPropertyTypes
.
useStore
Applicable imports
import { useStore } from 'zustand'
import { useStore } from 'zustand/react'
Change
- useStore:
- { <State>(store: StoreApi<State>): State
- , <State, StateSlice>
- ( store: StoreApi<State>
- , selector: StateSelector<State, StateSlice>,
- , equals?: EqualityChecker<StateSlice>
- ): StateSlice
- }
+ useStore:
+ <Store, StateSlice = ExtractState<Store>>
+ ( store: Store
+ , selector?: StateSelector<State, StateSlice>,
+ , equals?: EqualityChecker<StateSlice>
+ )
+ => StateSlice
Migration
If you are not passing any type parameters to useStore
,
no migration is required.
If you are, it's recommended to remove all the type parameters, or pass the store type instead of the state type as the first parameter.
UseBoundStore
Applicable imports
import type { UseBoundStore } from 'zustand'
import type { UseBoundStore } from 'zustand/react'
Change
- type UseBoundStore<
- State,
- Store = StoreApi<State>
- > =
- & { (): T
- , <StateSlice>
- ( selector: StateSelector<State, StateSlice>
- , equals?: EqualityChecker<StateSlice>
- ): U
- }
- & Store
+ type UseBoundStore<Store> =
+ & (<StateSlice = ExtractState<S>>
+ ( selector?: (state: ExtractState<S>) => StateSlice
+ , equals?: (a: StateSlice, b: StateSlice) => boolean
+ ) => StateSlice
+ )
+ & S
Migration
Replace UseBoundStore<T>
with UseBoundStore<StoreApi<T>>
,
and UseBoundStore<T, S>
with UseBoundStore<S>
UseContextStore
Applicable imports
import type { UseContextStore } from 'zustand/context'
Change
- type UseContextStore
Migration
Use typeof MyContext.useStore
instead
createContext
Applicable imports
import createContext from 'zustand/context'
Change
createContext:
- <State, Store = StoreApi<State>>() => ...
+ <Store>() => ...
Migration
Replace createContext<T>()
with createContext<StoreApi<T>>()
,
and createContext<T, S>()
with createContext<S>()
.
combine
, devtools
, subscribeWithSelector
Applicable imports
import { combine } from 'zustand/middleware'
import { devtools } from 'zustand/middleware'
import { subscribeWithSelector } from 'zustand/middleware'
Change
- combine:
- <T, U>(...) => ...
+ combine:
+ <T, U, Mps, Mcs>(...) => ...
- devtools:
- <T>(...) => ...
+ devtools:
+ <T, Mps, Mcs>(...) => ...
- subscribeWithSelector:
- <T>(...) => ...
+ subscribeWithSelector:
+ <T, Mps, Mcs>(...) => ...
Migration
If you are not passing any type parameters
to combine
, devtools
, or subscribeWithSelector
,
no migration is required.
If you are, remove all the type parameters, as they are inferred automatically.
persist
Applicable imports
import { persist } from 'zustand/middleware'
Change
- persist:
- <T, U = Partial<T>>(...) => ...
+ persist:
+ <T, Mps, Mcs, U = T>(...) => ...
Migration
If you are passing any type parameters, remove them as they are inferred automatically.
Next, if you are passing the partialize
option,
there is no further steps required for migration.
If you are not passing the partialize
option,
you might see some compilation errors.
If you do not see any,
there is no further migration required.
The type of partialized state is now T
instead of Partial<T>
,
which aligns with the runtime behavior of the default partialize
,
which is an identity (s => s
).
If you see some compilation errors,
you have to find and fix the errors yourself,
because they might be indicative of unsound code.
Alternatively, the workaround will be passing
s => s as Partial<typeof s>
to partialize
.
If your partialized state is truly Partial<T>
,
you should not encounter any bugs.
The runtime behavior has not changed, only the types are now correct.
redux
Applicable imports
import { redux } from 'zustand/middleware'
Change
- redux:
- <T, A>(...) => ...
+ redux:
+ <T, A, Mps, Mcs>(...) => ...
Migration
If you are not passing any type parameters to redux
,
no migration is required.
If you are,
remove all the type parameters,
and annotate only the second (action) parameter.
That is, replace redux<T, A>((state, action) => ..., ...)
with redux((state, action: A) => ..., ...)
.