State manipulation with ReactJS

React’s documentation tells us not to manipulate state of components directly, and that we should use setState() to handle any changes. This has two big “gotchas!” that I encountered.

1) setState() is asynchronous, and there is no guarantee it will be executed before any subsequent statements / function calls

This was something I encountered early on, when starting to break apart large code chunks into smaller functions. What was initially one setState() at the end of the large chunk – which worked fine – became a setState() at the end of each smaller function. This was causing it to fire out of sequence, and giving me results that I didn’t want.

2) JavaScript is BOTH pass by reference AND pass by value

This was a very confusing issue to track down, and took some testing to figure out that what looked right, and worked right, was actually still wrong.

  • If you pass a string to a function, you get a copy of the string.
  • If you pass a complex object to a function, you get a reference to the object.

Those parts were fine, and I had no problem with those concepts, since I’d seen them before. What I didn’t know was if you create a new variable and set it equal to a complex object, it is still just a pointer to that object.

Take this state changing code, executed inside of a function:

const tempState = this.state

tempState.firstName = in_value

this.setState(tempState)

This reads as roughly:

  • Create a new variable called tempState
  • Give it the value of the object this.state
  • Change the firstName property of the tempState value to an input parameter from the function
  • Set the component state to the tempState value

But what actually happens is:

  • tempState is a reference to this.state
  • The tempState.firstName on line 2 sets the value of this.state to the input parameter from the function
  • A side effect of this is that both tempState AND this.state will show firstName as updated
  • Calling this.setState() does not actually change the state, since it is still the same. But it does make the component re-render!

I verified this by removing this.setState(), which did not update the page, but when looking at the console log, it did update the value of both! This is because tempState is a reference to this.state, so any manipulation of one will manipulate “both”!

To correctly copy the state into a temporary object to manipulate, I use lodash / underscore’s .clone() or .cloneDeep() method.

const tempState = _.clone(this.state)

tempState.firstName = in_value

this.setState(tempState)