I'm creating a generic function to change the state.
The error message:
TS2345: Argument of type '() => { [x:string]: string }' is not assignable to parameter of type 'IUserSignUpState | Pick<IUserSignUpState, "url" | "errors"> | ((prevState: Readonly<IUserSignUpState>, props: Readonly<IUserSignUpState>) => IUserSignUpState | Pick<...> | null) null'.
Type '() => { [x: string] string; }' is not assignable to type '(prevState: Readonly<IUserSignUpState>, props: Readonly<IUserSignUpState>) => IUserSignupState | Pick<IUserSignUpState>, "url" | "errors" | null'.
Type '{ [x: string]: string; }' is not assignable to type 'IUserSignUpState | Pick<IUserSignUpState, "url" | "errors"> | null'.
Type '{ [x: string]: string; }' is missing the following properties from type 'Pick<IUserSignUpState, "url" | "errors">': url, errors.
This is the example of the generic change function:
./UserSignUp.tsx
interface IUserSignUpState {
url: string;
errors: string[];
// following can be used, but will lose type checking (*)
// [key: string]: string;
}
class UserSignUp extends React.Component<{}, IUserSignUpState>
state = {
url: '',
name: '',
errors: [],
};
render() {
const { url, errors } = this.state;
return (
<form>
<input type="text" name="url" value={url} onChange={this.change} />
<input type="text" name="name" value={name} onChange={this.change} />
<form>
)
}
change = (event:React.ChangeEvent<HTMLInputElement>) => {
const name = event.target.name;
const value = event.target.value;
// gives error here:
this.setState(() => {
return {
[name]: value
}
})
}
};
In this example the change event should only be allowed to update url: string;
and name: string;
, and not errors: []
.
Should I be defining the 'url' and 'name' types and reuse that in the change function somehow?
*In the Typescript documentation it states that Indexable types can be used. However, by doing so I will lose type checking. And a side-effect is that I could potentially also set the 'error' state, which typically shouldn't be possible from the change function.
update: based on the answer in this Stackoverflow Question, the following solution is possible:
split up the interface:
interface IInputState {
url: string;
}
interface IUserSignUpState extends IInputState {
errors: string[];
}
And re-use that interface, by either:
this.setState({ [name]: value } as Partial<IInputStates>)
or:
const name = event.target.name as keyof IInputStates;
const value = event.target.value;
this.setState(():IInputStates => {
return { [name]: value }
});