For the next few posts in this series, we’ll be diving deeper into the State Hook, useState. I’ll be covering some different situations in which the useState Hook can be used, providing examples for each.

To start, this post will go over how to implement useState with previous state. Before reading on, make sure to check out Parts 1 and 2 of this series in case you are unfamiliar with React Hooks.

Example 1

For this post, we’ll look at how to set state based on the previous state value. I’m going to stick with the counter example, only this time there will be buttons for increasing, decreasing and resetting the count value. Take a look at the following code:

// HookCounterTwo.jsimport React, {useState} from 'react';function HookCounterTwo() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
return (
<div>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>Increase</button>
<button onClick={() => setCount(count - 1)}>Decrease</button
</div>
)
}
export default HookCounterTwo// App.jsimport React from 'react';
import './App.css';
import HookCounterTwo from './HookCounterTwo';
function App() {
return (
<div className='App'>
<HookCounterTwo />
</div>
)
}
export default App

Let’s go over what we’re doing here. First, we create a functional component, HookCounterTwo. We then import useState from React, and use the Hook to create a state variable along with the corresponding setter function. Next, we add the JSX, setting the count itself and then three buttons for resetting, increasing and decreasing the count value.

For the Reset button, we set onClick to an arrow function calling setCount, passing in initialCount as the argument. This will set the count value back to 0. For the Increase button, we again set onClick to an arrow function calling setCount. But this time, for the argument, we’re passing in count + 1. And for the Decrease button, it’s set up the same as the Increase button only we’re passing in count -1 instead.

After rendering the HookCounterTwo component in App.js and taking a look at the browser, you’ll see that we have a working counter with appropriate buttons and functionality. However, there is an even better way to implement this same counter which we’ll cover below. But first, let’s take a look at why the above example is considered unsafe, or not the right way of changing the count value, although it looks like it is working.

Why Above Code Should be Avoided

Let’s take the above example and add an unlikely piece of code. See additions below in bold:

// HookCounterTwo.jsimport React, {useState} from 'react';function HookCounterTwo() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const incrementFive = () => {
for(let i = 0; i < 5; i++) {
setCount(count + 1)
}
}
return (
<div>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>Increase</button>
<button onClick={() => setCount(count - 1)}>Decrease</button>
<button onClick={incrementFive}>Increment 5</button>
</div>
)
}
export default HookCounterTwo

Here, we’re adding another button that increments the count value by 5. The onClick is calling a function, called incrementFive, which we define above the return statement.

In this function, we have a for loop that calls setCount and increases count by 1 every time. Instead of increasing by a value of 5, we’re just looping 5 times and incrementing by 1 every time.

If you go back to the browser now to test this new button out, you’ll see that the count value is still increased by only 1. This is because the setCount method is reading a stale value of the count state variable. Let’s see how we can overcome this by taking a look at Example 2 below.

Example 2

In order to avoid the above error, we need to use the second form of the setCount function. Instead of passing in a value of the new state variable, we can pass in a function that has access to the old state value. See below changes in bold:

const incrementFive = () => {
for(let i = 0; i < 5; i++) {
setCount(prevCount => prevCount + 1)
}
}
return (
<div>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount +
1
)}>Increase</button>
<button onClick={() => setCount(prevCount => prevCount -
1
)}>Decrease</button>
<button onClick={incrementFive}>Increment 5</button>
</div>
)

This time around, setCount accepts a function that has access to the old count value, and increments that old value by 1. prevCount is the argument, and the function body is prevCount + 1. Now when you go back to the browser and click the Increment 5 button, you’ll see the count value correctly increases by 5.

Any time you need to update a state value based on the previous state value, always go with the safer option of passing in a function that will set the new state value. We can also change our original Increase and Decrease button onClick’s as well, to the prevCount function.

Summary

To recap, when you have to update state based on the previous state value, always pass in a function to the state setter like we did in Example 2. Thanks for reading and see you next time for Part 4!

Software Engineer/Full Stack Web Developer | Ruby, JavaScript, React, Redux