Home > other >  TypeScript (react) : Dynamic update of object with spread operator
TypeScript (react) : Dynamic update of object with spread operator

Time:01-20

I have a custom type set

export type Menu =  {
    search: boolean,
    browse: boolean,
}

IM using zustand to hold state like so

interface AppState {
    menu: Menu;
    setMenu: (newMenu:Menu) => void
}
export const useStore = create<AppState>((set: SetState<AppState>, get: GetState<AppState>) => ({
    menu: {search: false, browse: false},
    setMenu: (newMenu:Menu):void => {
        set({menu: newMenu})
    }
}))

now in component i want to update this state onClick

const handleClick = (key: string) => {
        return (event: React.MouseEvent) => {
            setMenu({...menu, [key]: !menu[key]})
        }
    }


<div className="main-button menu-button" onClick={handleClick("search")}>

Logic is to simply toggle appropriate key: value pair to true or false. TypeScript throws error at

setMenu({...menu, [key]: !menu[key]})

I understand it wont allow because it cant guarantee menu[key] exists. If I hardcode / write menu['search'] it works. What is the best course of action in this case to make it work?

CodePudding user response:

The problem is that key is of type string, and as you said, may be a string with a name that's not valid in the interface.

If you're always supplying these names as hardcoded values as in your example, you an make it keyof Menu instead (which also has the convenient effect of TypeScript telling you when you call handleClick with an invalid value):

const handleClick = (key: keyof Menu) => {
// −−−−−−−−−−−−−−−−−−−−−−−^

If you have to allow string for key in handleClick, then you could use either a type guard function or a type assertion function. Then handleClick would use it, either in an if (the type guard) or just inline (the type assertion).

Type guard:

function isValidMenuKey(key: string): key is keyof Menu {
    return key === "search" || key === "browse";
}
// ...
const handleClick = (key: string) => {
    if (isValidMenuKey(key)) {
        return (event: React.MouseEvent) => {
            setMenu({...menu, [key]: !menu[key]});
        };
    } else {
        // Do what? (See type assertion version below)
    }
};

Type assertion:

// Or type assertion:
function assertIsValidMenuKey(key: string): asserts key is keyof Menu {
    if (key !== "search" || key !== "browse") {
        throw new Error(`Invalid key for Menu: "${key}"`);
    }
}
// ...
const handleClick = (key: string) => {
    assertIsValidMenuKey(key);
    return (event: React.MouseEvent) => {
        setMenu({...menu, [key]: !menu[key]});
    };
};
  •  Tags:  
  • Related