allow typescript compiler to call setState on only one react state property

ReactjsTypescript

Reactjs Problem Overview


I'm using Typescript with React for a project. The Main component gets passed state with this interface.

interface MainState {
  todos: Todo[];
  hungry: Boolean;
  editorState: EditorState;  //this is from Facebook's draft js
}

However, the code below (only an excerpt) won't compile.

class Main extends React.Component<MainProps, MainState> {
  constructor(props) {
    super(props);
    this.state = { todos: [], hungry: true, editorState: EditorState.createEmpty() };
  }
  onChange(editorState: EditorState) {
    this.setState({
      editorState: editorState
    });
  }
}

The compiler complains that, in the onChange method where I am only trying to setState for one property, the property todos and the property hungry is missing in type { editorState: EditorState;}. In other words, I need to set the state of all three properties in the onChange function to make the code compile. For it to compile, I need to do

onChange(editorState: EditorState){
  this.setState({
    todos: [],
    hungry: false,
    editorState: editorState
  });
}

but there's no reason to set the todos and the hungry property at this point in the code. What is the proper way to call setState on only one property in typescript/react?

Reactjs Solutions


Solution 1 - Reactjs

Edit

The definitions for react were updated and the signature for setState are now:

setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;

Where Pick<S, K> is a built-in type which was added in Typescript 2.1:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}

See Mapped Types for more info.
If you still have this error then you might want to consider updating your react definitions.

Original answer:

I'm facing the same thing.

The two ways I manage to get around this annoying issue are:

(1) casting/assertion:

this.setState({
	editorState: editorState
} as MainState);

(2) declaring the interface fields as optional:

interface MainState {
	todos?: Todo[];
	hungry?: Boolean;
	editorState?: EditorState;
}

If anyone has a better solution I'd be happy to know!


Edit

While this is still an issue, there are two discussions on new features that will solve this problem:
Partial Types (Optionalized Properties for Existing Types)
and
More accurate typing of Object.assign and React component setState()

Solution 2 - Reactjs

Update state explicitly, example with the counter

this.setState((current) => ({ ...current, counter: current.counter + 1 }))

Solution 3 - Reactjs

I think that the best way to do it is to use Partial

Declare your component in the following way

class Main extends React.Component<MainProps, Partial<MainState>> {
}

Partial automatically changes all of the keys to optional.

Solution 4 - Reactjs

We can tell setState which field it should expect, by parametrising it with <'editorState'>:

this.setState<'editorState'>({
  editorState: editorState
});

If you are updating multiple fields, you can specify them like this:

this.setState<'editorState' | 'hungry'>({
  editorState: editorState,
  hungry: true,
});

although it will actually let you get away with specifying only one of them!

Anyway with the latest TS and @types/react both of the above are optional, but they lead us to...


If you want to use a dynamic key, then we can tell setState to expect no fields in particular:

this.setState<never>({
  [key]: value,
});

and as mentioned above, it doesn't complain if we pass an extra field.

(GitHub issue)

Solution 5 - Reactjs

Edit: DO NOT USE this solution, prefer https://stackoverflow.com/a/41828633/1420794 See comments for details.

Now that spread operator has shipped in TS, my preferred solution is

this.setState({...this.state, editorState}); // do not use !

Solution 6 - Reactjs

I don't like casting and workarounds that gives you a lot place to make a mistake while you're scaling your project. Here'is my solution.

  1. We have to get proper intellisense for key by using generic type > <K extends keyof MainState>

  2. Then, we have to get proper type for value based on entered key > typeof MainState[K]

  3. In order to get code without and an error and without using casting/workarounds etc, pass callback into this.setState

  4. Combining all together


onChange = <K extends keyof MainState>(fieldName: K, value: typeof MainState[K]) => {
    	this.setState((prevState) => {
    		return {
    			...prevState,
    			[fieldName]: value,
    		};
    	});
    };

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLeahcimView Question on Stackoverflow
Solution 1 - ReactjsNitzan TomerView Answer on Stackoverflow
Solution 2 - ReactjsAlex CraftView Answer on Stackoverflow
Solution 3 - ReactjsnibaView Answer on Stackoverflow
Solution 4 - ReactjsjoeytwiddleView Answer on Stackoverflow
Solution 5 - ReactjsmlorberView Answer on Stackoverflow
Solution 6 - ReactjsKas ElvirovView Answer on Stackoverflow