Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript issue when trying to combine multiple middlewares in v4.1.1 #1242

Closed
mkuklis opened this issue Aug 30, 2022 · 6 comments
Closed

Comments

@mkuklis
Copy link

mkuklis commented Aug 30, 2022

I tried to follow the docs from here: https://docs.pmnd.rs/zustand/guides/typescript#using-middlewares

but when I try to combine multiple middlewares like:

import create from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const myMiddlewares = (f) => devtools(persist(f))

I'm getting:

Argument of type 'StateCreator<unknown, [], [["zustand/persist", unknown]], unknown>' is not assignable to parameter of type 'StateCreator<unknown, [["zustand/devtools", never]], [["zustand/persist", unknown]], unknown>'.
  Type 'StateCreator<unknown, [], [["zustand/persist", unknown]], unknown>' is not assignable to type '(setState: <A extends string | { type: unknown; }>(partial: unknown, replace?: boolean | undefined, action?: A | undefined) => void, getState: () => unknown, store: WithDevtools<StoreApi<unknown>>, $$storeMutations: [...]) => unknown'.
    Types of parameters '$$storeMutations' and '$$storeMutations' are incompatible.
      Type '[["zustand/devtools", never]]' is not assignable to type '[]'.

Is there any workaround for this?

@mkuklis
Copy link
Author

mkuklis commented Aug 30, 2022

Ah never mind I did not read the docs closely enough. I see that something like this works just fine:

const useBearStore = create<BearState>()(
  devtools(
    persist((set) => ({
      bears: 0,
      increase: (by) => set((state) => ({ bears: state.bears + by })),
    }))
  )
);

@mkuklis mkuklis closed this as completed Aug 30, 2022
@mkuklis
Copy link
Author

mkuklis commented Aug 30, 2022

Reading the docs closer it says:

"Just make sure you're 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."

const myMiddlewares = (f) => devtools(persist(f))

It would be still nice to see a TypeScript example that illustrates that kind of setup. The myMiddlewares example is useful in situations when we have to deal with multiple stores and the myMiddlewares can be reused.

@devanshj, @@dai-shi I apologize for reaching out to you directly but would it be possible for you to show us what that more advanced typing in TS could look like?

@devanshj
Copy link
Contributor

devanshj commented Aug 30, 2022

See the advanced usage section in that same document.

For this case, it'd look like the following... But if you want to come up with types for such things yourself you'll first have to understand the types by reading #710.

import create, { StateCreator, StoreMutatorIdentifier, } from "zustand"
import { devtools, persist } from "zustand/middleware"

type MyMiddlewares =
  < T
  , Mps extends [StoreMutatorIdentifier, unknown][] = []
  , Mcs extends [StoreMutatorIdentifier, unknown][] = []
  >
    (f: StateCreator<T, [...Mps, ["zustand/devtools", never], ["zustand/persist", unknown]], Mcs>) =>
      StateCreator<T, Mps, [["zustand/devtools", never], ["zustand/persist", T], ...Mcs]>

const myMiddlewares = (f => devtools(persist(f))) as MyMiddlewares

interface BearState {
  bears: number
  increase: (by: number) => void
}

const useBearStore = create<BearState>()(
  myMiddlewares((set) => ({
    bears: 0,
    increase: (by) => set((state) => ({ bears: state.bears + by })),
  }))
)

@mkuklis
Copy link
Author

mkuklis commented Aug 30, 2022

Thank you for your time and for responding @devanshj. This is very helpful.

@devanshj
Copy link
Contributor

No problems!

@mikebridge
Copy link

Don't know if this helps anyone else, but I found that if I want to combine the middleware types with slices, they each need to be explicitly typed according to the documentation:

import create, {StateCreator, StoreMutatorIdentifier} from 'zustand'
import {devtools, persist} from "zustand/middleware";

interface BearSlice {
  bears: number
  addBear: () => void
  eatFish: () => void
}
interface FishSlice {
  fishes: number
  addFish: () => void
}
type CombinedState = BearSlice & FishSlice

type MyMiddlewares =
  < T
    , Mps extends [StoreMutatorIdentifier, unknown][] = []
    , Mcs extends [StoreMutatorIdentifier, unknown][] = []
    >
  (f: StateCreator<T, [...Mps, ["zustand/devtools", never], ["zustand/persist", unknown]], Mcs>) =>
    StateCreator<T, Mps, [["zustand/devtools", never], ["zustand/persist", T], ...Mcs]>

const myMiddlewares = (f => devtools(persist(f))) as MyMiddlewares

const createBearSlice: StateCreator<
  CombinedState,
  [],
  [],
  BearSlice
  > = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

const createFishSlice: StateCreator<
  CombinedState,
  [],
  [],
  FishSlice
  > = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

export const useStoreWithMiddlewareAndSlices = create<CombinedState>()(myMiddlewares((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
})))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants