reactjstypescriptelectronzustand

zustand state function to change one property of object


So i am using Zustand with react in my electron app. I'm using it to pull some data from the dexie database. It's just one entry and it saves it to state. I'm trying to get the set function from the render component to be able to change the state of its respective property in state. In react useState this code works...

setMyState({...appSettings, [val]: data})

but in zustand my state looks like this and this code does not work

setAppSettings: (data: unknown, val: string) => {
        set((state) => ({...state.appSettings, [val]: data}))
}

Webstorm is telling me "Argument of type is not assignable to parameter of type". I have changed the "data" type to any and it has no effect. When i run this code and click on the button to change it nothing happens, not even an error. I did change it to this code with same result, nothing happens.

setAppSettings: (data: any, val: string) => {
        set((state) => ({
            appSettings: {...state.appSettings, [val]: data}
        }))
},

with this second code i get no errors in Webstorm. So its unclear whats happening as console isn't giving me anything. Also, the state doesn't appear to be updating. I add a button that console.logs the entire 'appSettings' object and it remains unchanged.

I feel like this is a simple solution but i have been banging my head against the wall to figure this out... Does anyone have any insight on this?

------Edit for first asnwer I'm not so sure the data type is the issue? i think its more im not to familiar with how zustand works? but the function that attempts to saves state is...

// store.ts
type AppSettings = {
  appSettings: Settings;
  setAppSettings: (role: string, data: unknown) => void;
  getAppSettings: () => void;
}
export const useAppSettings = create<AppSettings>((set) => ({
  appSettings: {
      id: 0,
      sortByTime: false,
      sortByFirstName: false,
      colorMode: '',
      hours: 24,
  },
  setAppSettings: (role: string, data: unknown) => {
      set((state) => ({
          appSettings: {...state.appSettings, [role]: data}
      }))
  },
  getAppSettings: async () => {
      const result = await db.settings.toCollection().first();
      if (result === undefined) {
          console.log("No settings in db, using default")
      } else {
          set(() => ({appSettings: result}))
      }
  },
}))
//Interface for settings object - in another file
export interface Settings {
  id: number,
  sortByTime: boolean,
  sortByFirstName: boolean,
  colorMode: string,
  hours: number,
}

// This buttonCreate function is used to duplicate 4 buttons that 
// all look alike and have the same operation. 

const setSettings = useAppSettings((state) => state.setAppSettings)

const buttonCreate = (forRole: string, con1: any, con2: any) => {
  return (
    <ToggleButtonGroup type="radio" name={forRole} className="mb-2 warning" value={settings[forRole]}>
       <ToggleButton id={forRole + "-toggle-1"} value={con1}
                     className={"btn-dark"} onClick={() => setSettings(forRole, con1)}>
                    {typeof con1 === "boolean" ? "Yes" : con1}
       </ToggleButton>
       <ToggleButton id={forRole + "-toggle-2"} value={con2}
                     className={"btn-dark"} onClick={() => setSettings(forRole, con2)}>
                    {typeof con2 === "boolean" ? "No" : con2}
       </ToggleButton>
    </ToggleButtonGroup>
  )
}

Solution

  • So in the first case:

    setAppSettings: (data: unknown, val: string) => {
            set((state) => ({...state.appSettings, [val]: data}))
    }
    

    Basically what happens, is that your state has some type like RootState, AppState or whatever.

    And

    Webstorm is telling me "Argument of type is not assignable to parameter of type". 
    

    What it means, that when you have [key] setter like that typescript can't cast that [key:string]: unknown to a type of your state, so it throws you Argument of type is not assignable

    To avoid that, yo can add as any but not to the data, to the result of function

    setAppSettings: (data: unknown, val: string) => {
            set((state) => ({...state.appSettings, [val]: data} as any))
    }
    

    Basically the object that is returned by (state) => should be typeof (state)

    There are ways around, like using as const, enums, typeof keyof, But needs more of your codebase to make a proper answer...

    Update

    There is your error:

    export type AppSettings = {
      appSettings: Settings;
      setAppSettings: (data: Settings, val: string) => void; // << data: Settings 
      getAppSettings: () => void;
    };
    
    // and then you override type:
      setAppSettings: (data: unknown, val: string) => ...
    

    And there you go a working code:

    import { create } from "zustand";
    import createSelectors from "./selectors";
    
    export type AppSettings = {
      appSettings: Settings;
      setAppSettings: (data: unknown, val: string) => void;
      getAppSettings: () => void;
    };
    
    interface Settings {
      id: number;
      sortByTime: boolean;
      sortByFirstName: boolean;
      colorMode: string;
      hours: number;
    }
    
    const appSettings = create<AppSettings>()((set) => ({
      appSettings: {
        id: 1,
        sortByTime: false,
        sortByFirstName: false,
        colorMode: "",
        hours: 0,
      },
      // u also had error here: 
      setAppSettings: (data, val) =>
        set((state) => ({
          ...state, // u need pass state here... 
          appSettings: { ...state.appSettings, [val]: data },
        })),
      getAppSettings: () => {},
    }));
    
    export const useAppSettings = createSelectors(appSettings);
    

    Fixed one more error.