First of all the code snippet you are going to see is a contrived example. I am just curious as to why the typing doesn't work.
interface MyState {
a: boolean;
b: number;
}
const defaultState: MyState = {
a: false,
b: 1
};
const keys = Object.keys(defaultState) as Array<keyof MyState>;
export default function App() {
const [myState, setMyState] = React.useState<MyState>(defaultState);
React.useEffect(() => {
keys.forEach((key) => {
setMyState((prev) => ({
...prev,
[key]: "lol". // should've given an error but it didn't
}));
});
}, []);
so I have a defaultState
and a list of its keys defined outside of the component. In the useEffect I am using the key to update the state(right now it is just changing every value to 'lol'). I passed a callback as the updater in the setMyState
and what it returns should be the new state. But here I make the value to be string, which violates the interface I had MyState
.
I wonder why it is not triggering the error
This is a live demo: https://codesandbox.io/s/sweet-dewdney-u83id?file=/src/App.tsx
Minimal example without react: Playground
The issue is not in using a callback to update the state - that's fine. The issue is that when you set a property of an object using a dynamic key, typescript doesn't know what the value of the key is for any given iteration of the loop, let alone what type its value should be. So you need to tell it. You need to dynamically type your keys using generics. There's a great article about it right here.
function updateValue<T extends keyof MyState, K extends MyState[T]>(
name: T,
value: K
) {
setMyState(prev => ({ ...prev, [name]: value }));
}
React.useEffect(() => {
keys.forEach((key) => {
updateValue(key, "lol");
});
}, []);
Gives the errors exactly as it should.
There are some interesting quirks about this. What if you set the value to one of the possible values on your interface?
keys.forEach((key) => {
updateValue(key, 4); // <---- no error, but there should be
});
In theory, this should also throw an error, because key
could be a
, in which case the types dont match. TypeScript doesn't throw an error. However, if you specify to run this only when key === 'a'
, the error does occur:
keys.forEach((key) => {
if (key === 'a'){
updateValue(key, 4); // <---- typechecking error occurs
}
});
In the latter case, typescript is smart enough to know that if the key is a
, the type has to be a number. But in the former case, where you are trying to assign a value that matches one of the types on the interface, but not all of them, we may be seeing a bug in, or reaching the limits of typescript.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With