In React, state must not be mutated directly, or, to say it in a dev-friendly way, state needs to be immutable. We don’t have to know all the inner-workings of React state to follow this guideline. It’s kind of like driving a car — if you want to stop or slow down, there is a set method of pushing the brake pedal, but we don't really need to know exactly how the brakes work to enjoy the benefits of using the pedal. You wouldn’t pull up the floor board and jam a crowbar into the drive shaft while throttling the hand brake. Nor would you toss an anchor out the window and hope it grabs onto something to stop the car. These methods may actually work, but will produce some strange (and dangerous) side effects and behaviors. Better to use the brakes and not mutate state.

For this example, say the data in our state looks something like this:

  messages: [
    {message: “First message”},
    {message: “Second message”},
    {message: “Third message”},
    {message: “Fourth message”}
]

If we want to add a new message onto the end, we would use concat:

  this.setState(prevState => ({
  messages: prevState.messages.concat(data.message)
}))

Pretty straight forward. But what happens when we want to wedge new data in-between old data? For example, under the second message. This gets complicated as we can’t concat or push because we would mutate the state.

First, we have to setup our click event which will set a variable to the item id, in this case we’ll be using data-id:

  handleClick = (event) => {
    let selected = event.target.dataset.id
}

Next, copy the entire state using Object.assign:

  handleClick = (event) => {
    let selected = event.target.dataset.id
    let copyState = Object.assign({}, this.state)
}

In order to place our new message in between the current messages, we need to grab all of the messages before and all of the messages after. To accomplish this, we use slice():

  handleClick = (event) => {
    let selected = event.target.dataset.id
    let copyState = Object.assign({}, this.state)
    let leftState = copyState.messages.slice(0, selected + 1)
    let rightState = copyState.messages.slice(selected + 1)
}

Finally, to complete our new object, we can use the ES6 spread operator and put everything together into a new array:

  handleClick = (event) => {
    let selected = event.target.dataset.id
    let copyState = Object.assign({}, this.state)
    let leftState = copyState.messages.slice(0, selected + 1)
    let rightState = copyState.messages.slice(selected + 1)
    let newState = [...leftState, {data.message}, …rightState]
    this.setState({
        messages: newState
    })
}

This solution works well for our simple example. Things get more complicated with nested data when a deep copy is necessary to update state. I’ll cover that topic in another post.


2,032 0 2