10

I have this interface defined as my state:

interface State {
  id: string;
  name: string;
  description: string;
  dimensionID: string;
  file: File | null;
  operator: string;
  isFormValid: boolean;
  filename: string;
};

I have a simple on change handler:

  update = (event: React.FormEvent<HTMLInputElement>): void => {
    const { name, value } = event.currentTarget;
    this.setState({ [name]: value });
  };

However, this error gets thrown:

Error:(109, 19) TS2345: Argument of type '{ [x: string]: string; }' is not assignable to parameter of type 'State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename"> | ((prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<...>)'.
  Type '{ [x: string]: string; }' is not assignable to type '(prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename">'.
    Type '{ [x: string]: string; }' provides no match for the signature '(prevState: Readonly<State>, props: Readonly<Props>): State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename">'.

My question is: How can I set state dynamically like how my on change handler tries? This handler is used for different parts of the form so I won't know which key of state I will need to update.

Update: SOLUTION From this post

  update = (event: React.FormEvent<HTMLInputElement>): void => {
    const { name, value } = event.currentTarget;
    this.setState(prevState => ({
      ...prevState,
      [name]: value,
    }))
  };

Works!

2
  • bind the name where ever you are calling update. this.update.bind(null, name ). so that in the update function you have both name and event as parameters Commented Oct 29, 2018 at 6:40
  • Check this out: github.com/DefinitelyTyped/DefinitelyTyped/issues/… Commented Oct 29, 2018 at 6:44

4 Answers 4

3

Most prefered solution casting.

this.setState({[name]: value} as {[P in keyof State]: State[P]})
// or
this.setState({[name]: value} as Pick<State, keyof State>)

setState with spread previous state

this.setState((prevState) => ({ ...prevState, [name]: value }));

This doesn't check the types. It's just a quick workaround for anyone who is stuck transpiling.

this.setState<never>({[name]: value})

Related links

React setState

Github Tim Roes comment

Sign up to request clarification or add additional context in comments.

Comments

2

The constant name is widened to string. Which version of TS are you using?

You cast it like this, but it's not a good practice though:

this.setState({
  [name]: value
} as Pick<State, keyof State>);

Bug related to this issue:

1 Comment

Thanks for the links! My solution came from the second link: github.com/Microsoft/TypeScript/issues/… It seems this is the same issue I'm experiencing. It doesn't look like it was solved with the latest version, though. I'm using v3.0.1.
1

eg.

update = (name: string, event: React.FormEvent<HTMLInputElement>): void => {
    const { value } = event.currentTarget;
    this.setState({ [name]: value });
  };

<input onChange={this.update.bind(null, 'first_name')} value={this.state.first_name} />

Comments

0

You can use the in keyof keywords to deconstruct the key / value from the IState object.

Create your React state:

interface IState { "email" : string; }

Create a form type that deconstruct the key as K and value as T[K]:

type FormState<T> = { [K in keyof T]: T[K] };
// T represents your IState
// to get the key use: [K in keyof T]
// to reference the value use: T[K]

Then pass the IState as a generic type argument to FormState type:

FormState<IState>

Now you can update the state, casting it to the above FormState:

this.setState({ [formStateItem]: (e.target.value as any) } as FormState<IState>);

In a handler it might look like this:

handleChange = <T, K extends keyof IState>(formStateItem: K) => {
    return (e: ChangeEvent<HTMLInputElement>) => {
        this.setState({ [formStateItem]: (e.target.value as any) } as FormState<IState>);
    }
};

And finally you can use in you JSX like this:

onChange={this.handleChange("email")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.