2

In this SO question, all the answers use this form of setState:

this.setState( { items: this.state.items.filter() } )

Which means this is a case of "new state is derived from the current state".

However, in this React Doc about async behavior of setState, in this case, this callback form MUST be used instead:

this.setState( currState => ( { items: currState.items.filter() } ) )

My question is, are those answer all wrong, and if so, what are the consequences? Or is it somehow still acceptable?

Minh Nghĩa
  • 854
  • 1
  • 11
  • 28
  • I mean `this.state.items` will always be the same value in a particular render pass... I personally don't really see when the async nature would be an issue... do you have an example? – Elias Feb 10 '22 at 10:01
  • 1
    I recommend you change the title, to be specifically about the async nature that was mentioned in the docs. You can still reference the issue linked in the question as an example. – Elias Feb 10 '22 at 10:16
  • bad practice vs good practice In some cases, the first option could lead to unwanted behavior – Ar26 Feb 10 '22 at 10:19
  • @Elias An example is exactly what I'm asking for :) – Minh Nghĩa Feb 10 '22 at 10:23
  • The linked question's proposed solution is using `array#filter` instead of `array#splice` which mutates the array. Using `array#filter` is in recommendation with react, since it returns new array and doesn't modify the existing array. IMO, both your approaches will work, since you are not mutating your array. – Hassan Imam Feb 10 '22 at 10:30
  • @HassanImam No it's not about mutating arrays. It's about having an up-to-date version of array to calculate the next state. – Minh Nghĩa Feb 10 '22 at 10:31
  • @HassanImam the question is about `this.setState( { items: this.state.items.filter() } )` vs `this.setState( currState => ( { items: currState.items.filter() } ) )` – Elias Feb 10 '22 at 10:36

1 Answers1

1

The issue only occurs if your code design is (what I think) "bad". Normally, you expect to update the value that is used in the current render. So for example, if the currently displayed num is 5, and you want to increase it, you would want the number to be 6. But if you now set the num to 10 before doing setNum(num + 1), the output won't be 11, but 6, because it used the currently rendered value (5) + 1 (Which is what I would expect). If you wanted it to use "the current state", so the 10 from the previous setNum, you would need to use the callback version.

That was sort of complicated, so here an example:

import React, { useState } from 'react';

export default ({ name }) => {
  const [num, setNum] = useState(5);

  return (
    <React.Fragment>
      <h1>{num}</h1>
      <button onClick={changed}>Change!</button>
      <button onClick={changedWithCallback}>Change with callback!</button>
      <button onClick={resetted}>Reset!</button>
    </React.Fragment>
  );

  function changed() {
    setNum(10);
    setNum(num + 1);
  }

  function changedWithCallback() {
    setNum(10);
    setNum((state) => state + 1);
  }

  function resetted() {
    setNum(5);
  }
};

Working example

Elias
  • 3,592
  • 2
  • 19
  • 42
  • If this is hard to understand (I'm not sure if I managed to explain it well), please leave a comment :) – Elias Feb 10 '22 at 10:34