Search code examples
reactjstypescriptsetstatetslint

React + TypeScript - how to update state with single object with a "dynamic" set of changes


I want to update the state at the end of a list of conditional updates and accumulates the changes to be committed at once (to avoid async issues with setState).

interface IMyComponentState {
    a: string;
    b: string;
    c: string;
}

(...)

updateState = (condition1: boolean, condition2: boolean) => {
    const stateChanges: Partial<IMyComponentState> = {};

    if (condition1) {
        stateChanges.a = 'someValue 1';
    }
    if (condition2) {
        stateChanges.b = 'someValue 2';
    }
    this.setState(
        { ...this.state, ...stateChanges },
        () => this.doSomethingThatDependsOnState()
    );
}

This works fine, but is there a way to do it without using this.state, like below?

    this.setState(
        {...stateChanges},
        (...)
    );

Here tslint is complaining that setState expects an object of type Pick<IMyComponentState, "a" | "b" | "c"> but that would require me to specify (and predict) the properties to be changed in advance, which is not possible. I heard that React's diffing algorithm checks the references, but I'm still concerned that putting the whole state object in setState would add an extra overhead or is unnecessary.


Solution

  • First of all, you don't need to spread this.state, React will only apply state changes to the specified keys.

    Secondly, the type for setState intentionally doesn't use Partial<T>, this is because setting undefined on a key explicitly will perform a state update, so it uses Pick (GitHub issue here talking more about it)

    To get around this issue, you can cast you state update to Pick<IMyComponentState, keyof IMyComponentState>;

    updateState = (condition1: boolean, condition2: boolean) => {
      const stateChanges = {};
    
      if (condition1) {
        stateChanges.a = 'someValue 1';
      }
      if (condition2) {
        stateChanges.b = 'someValue 2';
      }
      this.setState(
        { ...stateChanges } as Pick<IMyComponentState, keyof IMyComponentState>,
        () => this.doSomethingThatDependsOnState()
      );
    }