DCR
DCR

Reputation: 15665

proper way to use callback function and understanding arrow notation

In my increaseCount method I have 3 different ways of increasing the count. I thought the first method could be used twice but it doesn't work as it seems to merge the setState in the callback. What's the proper way to use a callback function and why does the arrow notation work? How is prevState.count being defined? It is never set to 0

import React from "react";
import { render } from "react-dom";

const styles = {
  fontFamily: "sans-serif",
  textAlign: "center"
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  increaseCount() {
    console.log(this);
    this.setState({ count: this.state.count }, function() {
      this.setState({ count: this.state.count + 1 });
    });

    this.setState({ count: this.state.count + 1 });

    this.setState(prevState=>({ count: prevState.count + 1 }));
  }

  render() {
    return (
      <div style={styles}>
        <div>
          <button onClick={() => this.increaseCount()}>Increase</button>
        </div>
        <h2>{this.state.count}</h2>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

Upvotes: 0

Views: 536

Answers (3)

FisNaN
FisNaN

Reputation: 2865

  • The increaseCount function need to be an arrow function, since you didn't bind it.
  • The setState is an async function, it will not execute immoderately after called. So the 2nd setState won't execute correctly. In general, it is better to setState once on a single event. Link to doc
  • this.setState(function() {}) is valid, but I haven't seen this.setState({ count: this.state.count }, function() {}) in anywhere in the doc.

To get expected result based on your code, this is the correct pattern:

increaseCount = () => {
  let countBuffer = this.state.count;
  countBuffer++;
  countBuffer++;
  countBuffer++;

  this.setState(prevState => ({ count: countBuffer }));
}

This should work, but it's not the preferred method, due to it requires multiple times of unnecessary rendering:

increaseCount = async () => {
  await this.setState(function(){ return { count: this.state.count + 1 } });
  await this.setState({ count: this.state.count + 1 });
  this.setState(prevState=>({ count: prevState.count + 1 }));
}

Upvotes: 1

CRice
CRice

Reputation: 32146

As far as I know, only your third method is a proper way to increment a state value. Going through your attempts:

this.setState({ count: this.state.count }, function() {
  this.setState({ count: this.state.count + 1 });
});

This snippet is redundant (you set the count to the same value), then when that state change has finished, you then increment the count by adding 1 to this.state.count. While that's fine here, you could have just done that to begin with (as in your next snippet).

Next:

this.setState({ count: this.state.count + 1 });

Better, but the setState method is asynchronous, so the actual state change won't occur until later. That means that this will work in most cases, but if you were to call it twice it would not increment by two, since this.state.count hasn't updated yet when the second call occurs.

Finally, your last attempt looks perfect:

this.setState(prevState=>({ count: prevState.count + 1 }));

This uses the functional setState syntax where react will give your callback function the current state, and you are expected to read it and return your desired new state. Using this style, you could call it twice (or as many as you want) times, and each call would increment the count properly.


To address some of your other questions:

it seems to merge the setState in the callback.

Correct. From the documentation linked below:

React may batch multiple setState() calls into a single update for performance.


What's the proper way to use a callback function and why does the arrow notation work?

The functional setState is a new-ish react feature. Arrow functions are nice but not required (unless you access this inside the callback). From their documentation here:

To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:

// Correct
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));

We used an arrow function above, but it also works with regular functions:

// Correct
this.setState(function(prevState, props) {
    return {
        counter: prevState.counter + props.increment
    };
});

How is prevState.count being defined? It is never set to 0

It is being set to zero in the constructor. The line:

this.state = {
  count: 0
};

is what sets it initially. From that point, the current state (whatever it may be) is passed your setState callback function.

Sorry for the wall, this answer got out of hand before I knew it. Hopefully that helps, LMK if I missed the point or there are more questions

Upvotes: 4

Tom Mendelson
Tom Mendelson

Reputation: 625

you should bind your callback function in order to use 'this'

your constructor should look like this:

constructor(props) {
    super(props);
    this.state = {
      count: 0
    };

    this.increaseCount = this.increaseCount.bind(this)
  }

Edit:

and your inreaseCount function should look like:

increaseCount() {
    console.log(this);
    this.setState(prevState=> ({ count: prevState.count + 1 })) ;
  }

Upvotes: 0

Related Questions