-
Notifications
You must be signed in to change notification settings - Fork 30.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[@types/react] cannot setState with dynamic key name type-safe #26635
Comments
This is a limitation of the compiler itself, inferred types via computed property keys do not support union types, it only supports dynSetState(key: StateKeys, value: string) {
this.setState({
[key]: value
} as Pick<State, keyof State>)
} This will still give an error appropriately if the value is not in the set of possible property value types, but will not give you an error if the keys are not within the set of possible property keys. |
exactly what @ferdaber although IMHO casting stuff like this is not very "good pattern", overall you should prefer updating state via callback pattern which again adheres to best practices, like extracting that callback outside the class to pure easy to test function :) Good: class C extends Component<{}, State> {
updateState(key: StateKeys, value: string) {
this.setState((prevState) => ({
...prevState,
[key]: value,
}));
}
} Better: const updateState = <T extends string>(key: keyof State, value: T) => (
prevState: State
): State => ({
...prevState,
[key]: value
})
class C extends Component<{}, State> {
doSomething(){
this.setState(updateState('password','123124'))
}
} |
I have not found related issue about this limitation in typescript repo.. Any chance you know if this has been discussed (and where) in typescript repo? Basically wondering if there are some plans to address this in future typescript versions. |
Here is the discussion, the design notes from the TS team, and an attempt at fixing it (which I believe was retracted for a future version): |
Indeed, what I'm trying to accomplish is to do something like this: https://reactjs.org/docs/forms.html#handling-multiple-inputs |
I had to add this.setState({
[key]: value
} as unknown as Pick<State, keyof State>) That warned if So I went for this shorter solution: this.setState<never>({
[key]: value
}) This warns if |
Why is #26635 (comment) working? I mean the key is still a union type? |
May be this will help you type IAction = {
[P in keyof IAppSettings]?: IAppSettings[P];
};
function reducer(state: IAppSettings, action: IAction) {
return {
...state,
...action,
};
} |
Is there any progress in this issue? |
I was able to get this working
|
For others looking at this and trying to apply it to their own state types, note that the type of interface State {
name: string;
age: number;
}
type StateKeys = keyof State;
function dynSetState<K extends StateKeys>(key: K, value: State[K]) {
this.setState({ [key]: value }); // fails; if only this worked...
this.setState({ [key]: value } as Pick<State, K>); // clean cast works
this.setState((s, _) => ({ ...s, [key]: value })); // avoids cast, but changes js
}
dynSetState("name", "a"); // works as expected
dynSetState("name", 1); // fails as expected
dynSetState("age", "a"); // fails as expected
dynSetState("age", 1); // works as expected
It works because the type of |
Just adding to @arinwt as an another example. TL;DR: Final Solution At Bottom Expanded Solution With Console Errors: type RegisterTypes = {
email: string;
password: string;
}
// ...
const [state, setState] = useState<RegisterTypes>({
email: "",
password: "",
});
// ...
const onChangeInput = (key: keyof RegisterTypes) => (event: React.ChangeEvent<HTMLInputElement>) => {
setState({
[key]: event.target.value
} as Pick<RegisterTypes, typeof key>);
};
// ...
<input type="email" onChange={onChangeInput('email')} value={state.email} /> Although this gives me the following error in the console: Warning: A component is changing a controlled input of type password to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. Alternative Solution (Cheating) With Console Errors: Also gives the warning too. type RegisterTypes = {
[key: string]: string;
}
// ...
const onChangeInput = (key: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
setState({
[key]: event.target.value
});
}; Final Solution No Errors: type RegisterTypes = {
email: string;
password: string;
}
// ...
const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
const newState = { ...state };
newState[event.target.name as keyof RegisterTypes] = event.target.value;
setState(newState);
};
// ...
<input name="email" type="email" onChange={onChangeInput} value={state.email} /> |
Hi thread, we're moving DefinitelyTyped to use GitHub Discussions for conversations the To help with the transition, we're closing all issues which haven't had activity in the last 6 months, which includes this issue. If you think closing this issue is a mistake, please pop into the TypeScript Community Discord and mention the issue in the |
If you know how to fix the issue, make a pull request instead.
@types/react
package and had problems.Definitions by:
inindex.d.ts
) so they can respond.If you do not mention the authors the issue will be ignored.
I cannot call
setState
with an object being created from computed property name with type-safety:I do aware of #18365, and the workaround in #18365 (comment) . However, when using the workaround, Typescript doesn't error out when it should:
The text was updated successfully, but these errors were encountered: