Search code examples
reactjstypescripttypescript-genericsreact-typescript

How to type a generic change handler with keof?


In many of my react forms I want to use a generic form change handler that can handle updating the different fields of a state object.

How do I correctly type this change handler so that the below invocations are resulting in a compile error:

interface Data {
    a: string;
    b: number;
}

let data: Data = {a: "a", b: 1};

function changeHandler<T>(field: keyof Data, value: T) {
    data = {...data, [field]: value}
}

// should compile
changeHandler("a", "aa");
changeHandler("b", 11);

// should not compile
changeHandler("a", 1);
changeHandler("b", "b");

console.log(data);

TS Playground

The example is simplified and in the real application React.useState is used instead of using a let variable.


Solution

  • Instead of adding a generic parameter for the value, add it for the key, and by using indexed access types get the correct type for the value:

    function changeHandler<T extends keyof Data>(field: T, value: Data[T]) {
      data = {...data, [field]: value}
    }
    

    Testing:

    changeHandler("a", "aa"); // no error
    changeHandler("b", 11); // no error
    
    changeHandler("a", 1); // error
    changeHandler("b", "b"); // error
    

    playground