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

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]});
    };
};