zustand: TypeScript type conflict with persist middleware

I am unable to get the persist middleware working with TypeScript if I define state properties with multiple types or with the boolean type.

zustand version: 3.6.4 TypeScript version: 4.1.3

Example code:

import create, { GetState, SetState } from "zustand";
import { persist, StoreApiWithPersist } from "zustand/middleware";

type AuthStore = {
  token: string | undefined;
  authenticated: boolean;
  authenticate: (username: string, password: string) => Promise<void>;
}

const useAuthStore = create<AuthStore, SetState<AuthStore>, GetState<AuthStore>, StoreApiWithPersist<AuthStore>>(
  persist(
    (set) => ({
      token: undefined,
      authenticated: false,
      authenticate: async (username, password) => {
        set({authenticated: true})
      },
    }),
    { name: "auth-store" }
  )
);

export default useAuthStore;

persist function gives the following errors:

 Type 'string | undefined' is not assignable to type 'undefined'.
          Type 'string' is not assignable to type 'undefined'.ts(2345)

 The types of 'authenticated' are incompatible between these types.
        Type 'boolean' is not assignable to type 'false'.ts(2345)

Everything works as expected if I just define token and authenticated as any type.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 17 (9 by maintainers)

Most upvoted comments

Here’s another workaround:

    type MyState = {
      token: string | undefined
      authenticated: boolean
      authenticate: (username: string, password: string) => Promise<void>
    }
    type MyPersist = (
      config: StateCreator<MyState>,                                            
      options: PersistOptions<MyState>                                          
    ) => StateCreator<MyState>                                                  
    const useStore = create<MyState>(                                           
      (persist as MyPersist)(                                                   
        (set) => ({                                                             
          token: undefined,                                                     
          authenticated: false,                                                 
          authenticate: async (_username, _password) => {                       
            set({ authenticated: true })                                        
          },                                                                    
        }),                                                                     
        { name: 'auth-store' }                                                  
      )                                                                         
    )

Basically, we type cast to that of v3.6.2 which didn’t have persist api. I will modify #651 to show this as a possible workaround.

I’m so sorry for wasting your time, I used TypeScript without adding the extra parenthesis 😦

I should’ve read the docs 😃 The thing is, I used the non-TypeScript version in TypeScript and my editor went nuts. I ,mistakenly thought it was a bug.

https://docs.pmnd.rs/zustand/guides/typescript

“The difference when using TypeScript is that instead of writing create(...), you have to write create<T>()(...) (notice the extra parenthesis () too along with the type parameter) where T is the type of the state to annotate it”

Thanks for reporting. With v3.6.5, if you don’t need the persist api types, you should be able to do this:

const useAuthStore = create<AuthStore>(
  persist(
    (set) => ({
      token: undefined,
      authenticated: false,
      authenticate: async (username, password) => {
        set({authenticated: true})
      },
    }),
    { name: "auth-store" }
  )
);

If you need a custom store api type, it’s a bit tricker. see #632 and: https://github.com/pmndrs/zustand/blob/21a28ff13eb3d13f2944642287e136de998dd8ab/tests/middlewareTypes.test.tsx#L573

Here’s another workaround:

    type MyState = {
      token: string | undefined
      authenticated: boolean
      authenticate: (username: string, password: string) => Promise<void>
    }
    type MyPersist = (
      config: StateCreator<MyState>,                                            
      options: PersistOptions<MyState>                                          
    ) => StateCreator<MyState>                                                  
    const useStore = create<MyState>(                                           
      (persist as MyPersist)(                                                   
        (set) => ({                                                             
          token: undefined,                                                     
          authenticated: false,                                                 
          authenticate: async (_username, _password) => {                       
            set({ authenticated: true })                                        
          },                                                                    
        }),                                                                     
        { name: 'auth-store' }                                                  
      )                                                                         
    )

Basically, we type cast to that of v3.6.2 which didn’t have persist api. I will modify #651 to show this as a possible workaround.

Thanks for the workaround!

For anyone having an issue with the partialize, here is a slightly modified solution to fix it.

    type MyState = {
      token: string | undefined
      authenticated: boolean
      authenticate: (username: string, password: string) => Promise<void>
    }
    type MyPersist = (
      config: StateCreator<MyState>,                                            
      options: PersistOptions<MyState, Partial<MyState>>                                          
    ) => StateCreator<MyState>                                                  
    const useStore = create<MyState>(                                           
      (persist as MyPersist)(                                                   
        (set) => ({                                                             
          token: undefined,                                                     
          authenticated: false,                                                 
          authenticate: async (_username, _password) => {                       
            set({ authenticated: true })                                        
          },                                                                    
        }),                                                                     
        { name: 'auth-store' }                                                  
      )                                                                         
    )

Thank you @dai-shi I like that workaround

Thanks for chiming in. Hmm, I thought I tested in tests. As you say, it can repro in csb: https://codesandbox.io/s/agitated-cohen-g8r4f?file=/src/App.tsx

🤔 🤔 🤔