How to increment and append state variables in React

Last updated : August 25, 2022

Understanding how the React state updates work is essential to work with state variables correctly. This article explains the correct ways to increment and append to React state variables.

Incrementing a state variable

No matter what task you perform on state variables, do not directly mutate the state variable. Always use the React-provided setter method to update the state. Let's take a look at a few examples.

Why does updating the state with postfix doesn't work?

Take a look at the below example. If you run this code, you will find that the counter updates every other click.

import { useEffect, useState } from "react"
export const StateUpdateExamples = () =>{
  let [counter, setCounter] = useState<number>(0)
  const updateCounter = () => {
    setCounter(counter++) // WRONG
  }
 return(
  <>
  <div>{counter}</div>
  <div><button onClick={updateCounter}>Update</button></div>
  </>
 )
}

I am doing two things wrong here.

  1. Directly mutating the state
  2. Using postfix increment

Here is what happens in the first button click.

const updateCounter = () => {
    //counter = 0
    setCounter(counter++) // setCounter(0)
    //counter = 1
}

My setCounter gets the value 0 because of the postfix increment. 0 was the previous state value. Therefore no state update happens. On the second click, my setCounter gets the value 1, the previous value.

const updateCounter = () => {
    //counter = 1
    setCounter(counter++) // setCounter(1)
    //counter = 2
}

This time, I get a state update because my state changes from 0 to 1. This pattern follows for every consecutive button click.

How to increment a state variable?

In the above example, I updated the state with counter++. The counter is a state variable; I must not update it directly. I could use counter + 1 instead. That way, I don't update the counter state variable directly.

const updateCounter = () => {
    setCounter(counter+1)
}

My favorite is to use the previous state. That guarantees that I always get the up-to-date state variable value.

const updateCounter = () => {
    setCounter((prevCounter) => prevCounter+1)
}

Why do multiple state updates don't work?

Take a look at the below code. I call the setText multiple times in the updateText() function.

import { useEffect, useState } from "react"
export const StateUpdateExamples = () =>{
  let [text, setText] = useState<string>("")
  const updateText = () => {
    setText(text + "These ")
    setText(text + "updates ")
    setText(text + "are ")
    setText(text + "batched ")
  }
 return(
  <>
  <div>{text}</div>
  <div><button onClick={updateText}>Update</button></div>
  </>
 )
}

Under the hood, React batch those four state updates. When React executes them, first, React looks for the value of the text variable within the updateText() function's closure. That is an empty string. React uses this value to perform all four state updates. In other words, the value of the text variable is an empty string for all four state update statements. Therefore, we only see the last update "batched " in effect with every button click.

How to do multiple state updates?

There are many ways to update the state. If I build a string based on conditions, I will use a separate variable to build the string and update the state with the final string value.

const updateCounter = () => {
    let dynamicString = ""
    //Use any conditions to build the dynamicString
    dynamicString += "These "
    dynamicString += "updates "
    dynamicString += "are "
    dynamicString += "batched"
    setText(dynamicString)
}

If I want to keep appending the values to the state variable, I will use the previous state variable value to append the new value.

const updateCounter = () => {
    let dynamicString = ""
    //Use any conditions to build the dynamicString
    dynamicString += "These "
    dynamicString += "updates "
    dynamicString += "are "
    dynamicString += "batched"
    setText(prevText => prevText + dynamicString)
}

For some reason, If I want to update the state directly, I will use the previous state on each update statement. The previous value of the setText() method guarantees the up-to-date value for the text state variable.

const updateCounter = () => {
    setText(text => text+"These ")
    setText(text => text+"updates ")
    setText(text => text+"are ")
    setText(text => text+"batched ")
}
L Raney
By: L Raney
Lance is a software engineer with over 15 years of experience in full-stack software development.
Read more...

Comments are disabled

No Comments