Typescript + Immer + Slice pattern #1796
-
I would like to use zustand with typescript and immer plus the slice pattern. While it is working as expected, I get a TS error on the action in the slice. My store with bear slice: import { create, type StateCreator } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface BearsSlice {
bears: number
add: () => void
}
const useBearSlice: StateCreator<BearsSlice> = (set) => ({
bears: 0,
add: () => set(
(state) => { // <- typescript error, see bellow
state.bears += 1
}
),
})
const useBoundStore = create(
immer<BearsSlice>(
(...a) => ({
...useBearSlice(...a),
})
)
) TS error on the action
Any idea how to achieve it without the TS error? |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 15 replies
-
@u-rogel on ts you should use the curried version Solution create<BearsSlice>()(immer(...)) |
Beta Was this translation helpful? Give feedback.
-
@u-rogel here you go https://codesandbox.io/s/zustand-updating-draft-states-and-slice-pattern-basic-demo-3e9zby?file=/src/App.tsx For immer + Slice Pattern you need to create the next type helper type ImmerStateCreator<T> = StateCreator<
T,
[["zustand/immer", never], never],
[],
T
>; then you can use it like this const createBearSlice: ImmerStateCreator<BearSlice> = (set) => ({
bears: 0,
add: () => set(
(state) => {
state.bears += 1
}
),
}) I created a codesandbox example with the code that you need to achieve what you want (immer + slice pattern). BTW, I'll update the immer guide to include it.
|
Beta Was this translation helpful? Give feedback.
-
What would this pattern look like if I wanted specific middleware on my Slice (not all of the slices in my store)? For example, if I wanted to add Zundo middleware to my SpreadsheetSlice? Another use case would be middleware that takes parameters that are specific to each slice. |
Beta Was this translation helpful? Give feedback.
-
I ran into this problem with a store I created with immer middleware and two slices. The problem is that you need to derive the type of the slice function from the overall type of the Here is my solution to typing the slice so it still has access to outside state: // file ~/zustand/types.ts
import { StateCreator, StoreMutatorIdentifier } from 'zustand/vanilla';
import { Store } from '~/zustand/store';
export type ImmerStateCreator<
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = [],
> = StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>;
export type MyAppStateCreator = ImmerStateCreator<Store>;
// Defines the type of a function used to create a slice of the store. The
// slice has access to all the store's actions and state, but only returns
// the actions and state necessary for the slice.
export type SliceCreator<TSlice extends keyof Store> = (
...params: Parameters<MyAppStateCreator>
) => Pick<ReturnType<MyAppStateCreator>, TSlice>;
// file ~/zustand/store.ts
import {Slice, createSlice} from './slice';
export interface Store extends Slice {
foo: string;
setFoo: (input: string): void;
}
export const useStore = create<Store>()(
immer((set, get, store) => {
foo: '',
setFoo: (input) => { set(state => { state.foo = input; }); },
...createSlice(set, get, store);
})
);
// file ~/zustand/slice.ts
export interface Slice {
bar: string;
setBar: (input: string): void;
}
export const createSlice: SliceCreator<keyof Slice> = (set, get) => {
return {
bar: '',
setBar: (input) => {
const newValue = input + get().foo; // slice has access to the rest of the store
set(state => { state.bar = newValue; }); },
};
} |
Beta Was this translation helpful? Give feedback.
@u-rogel here you go https://codesandbox.io/s/zustand-updating-draft-states-and-slice-pattern-basic-demo-3e9zby?file=/src/App.tsx
For immer + Slice Pattern you need to create the next type helper
then you can use it like this
I created a codesandbox example with the code that you need to achieve what you want (immer + slice pattern). BTW, I'll update the immer guide to include it.