Functional setState()

Class Component

State is assigned using the class constructor

class Kitten extends Component {
  constructor() {
    super()
    this.state = {
      name: ''
    };
    ...
  }

  render() {
    return (
      <div>
        <h1>Meow, it's {this.state.name}'s Homepage</h1>
      </div>

      <div className="kitten">
        <img src={kitten} alt="cute kitten" />
      </div>
    );
  }
}

setState()
with Object

Update state by passing an object containing part of the state to setState()

class Kitten extends React.Component {
    ...

    setKittenName(name) {
        this.setState({
            name: name
        });
    }

    componentWillMount() {
      this.setKittenName('Greg');
    }
    
    render() {
    
        ...    
    
    }
}

But what if your next state depends on the previous state? 

Invoking setState() multiple times

class Puppy extends Component {
  constructor() {
    super()
    this.state = {
      name: '',
      treats: 0
    };
    ...
  }
    
  twoTreats() {
    this.setState({
      treats: this.state.treats + 1
    });
    this.setState({
      treats: this.state.treats + 1
    });
  } 
    ...

  render() {
    return (
        ...
        <h2>I want two treats!</h2>
        <div>        
          <button onClick={this.twoTreats}>
            Two treats please!
          </button>
          <div><h3>{this.state.treats}</h3></div>
        </div>
        ...
    );
  }
}

Demo of bad setState() example

React merges multiple setState() calls together form a single object

Object Composition

let puppy = { name: 'Fido' },
    dog   = { name: 'Fluffy' },
    tiger = Object.assign({}, puppy, dog);

console.log(tiger); // { name: 'Fluffy' }

When merging multiple objects with the same key, the value of the last object overrides the previous values

How can we fix our problem?

Functional
setState()!

Dan Abramov said so himself

First,

  • Move your methods to a class component and pass them down as props.
  • Also pass down key items from the state as props
class App extends React.Component {
    constructor() {
        super()
        this.state = {
            name: '',
            treats: 0
        };
    }
    ...
    
    twoTreats() {
        this.setState({
          treats: this.state.treats + 1
        });
        this.setState({
          treats: this.state.treats + 1
        });
    }

    render() {
        return (
            <Puppies twoTreats={this.twoTreats} 
                     treats={this.state.treats}
            />
        )
    }
  }
}

Second

Refactor this.setState() to pass in a function that accepts the previous state and a callback function as a second parameter

twoTreats() {
    this.setState((prevState, props) => ({
        treats: prevState.treats +1
    }));
    
    this.setState((prevState, props) => ({
        treats: prevState.treats +1
    }));
}

Third

Refactor the presentational component to use twoTreats() from props

const Puppies = (props) => {

  return (
    ...
      </div>
      <h2>I want two treats!</h2>
      <div>
        <button 
            onClick={props.twoTreats}>Two treats please!
        </button>
        <div><h3>{props.treats}</h3></div>
      </div>
    ...
  );
}

Demo of good setState()

Resources

  • Link
  • Link
  • Link

Functional setState()

By Fanny Jiang

Functional setState()

Learn about how to use functional setState to manage the state of your React app.

  • 138