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)