0

I was getting an issue where React wouldn't render after changing state by passing in an array, though the state successfully updated:

const [phoneNumber, setPhoneNumber] = useState([])
...
const newNumber = phoneNumber
newNumber.pop()
setPhoneNumber(newNumber)

However, after passing newNumber using the spread operator, the app rerendered as expected:

const newNumber = phoneNumber
newNumber.pop()
setPhoneNumber([...newNumber])

What causes this?

dvtrn
  • 3
  • 2
  • 2
    With the spread you clone the object (in this case an array) so react gets an entirely new reference where in the first case the reference never changes – Dominik Jun 23 '21 at 21:04

2 Answers2

5

You set newNumber to equal phoneNumber. This means newNumber is a reference to phoneNumber. When you pop newNumber you are popping phoneNumber. You are then essentially setting the state back to phoneNumber. Although the value has changed it is still the same reference, and so react assumes it does not need to re-render.

When you spread the array you are creating a new array (which has a new reference in memory), and so react re-renders the component.

This is an anti-pattern, and you should not be directly mutating the state value phoneNumber like this. Instead you should create a new value (e.g. when you spread it) and update the state with your new value.

Flagship1442
  • 1,688
  • 2
  • 6
  • 13
  • Understood. The way I addressed this is by setting: newNumber = [...phoneNumber] newNumber.pop() setPhoneNumber(newNumber) This seems a bit cumbersome. Is there a better way to do this? – dvtrn Jun 23 '21 at 21:24
  • Hmm, you could do `setPhoneNumber(phoneNumber.slice(0, phoneNumber.length - 1))`, but it's also a bit cumbersome. I normally use the `lodash` library for fiddly data manipulation things like this. They have a function for everything. In this case you could use `dropRight` https://lodash.com/docs/4.17.15#dropRight – Flagship1442 Jun 23 '21 at 21:34
2

React docs:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

An efficient way of telling when any props or states like an array, object, etc have changed automatically is, by the immutable data structure.

The idea behind using immutable data structures is simple. For complex data types, the comparison performs over their reference. Whenever an object containing complex data changes, instead of making the changes in that object, we can create a copy of that object with the changes which will create a new reference. ES6 has object spreading operator to make this happen.

For more info on the mutable state problem, read this.

Rukshan Jayasekara
  • 1,945
  • 1
  • 6
  • 26