In the previous article, we had a look at a basic example on how to implement the useState() hook. In this article, let's take a look at another example, this time we will learn how to set state based on the previous state value.

Once again we are going to be implementing a counter but this counter will have buttons to increment decrement and reset the count value.

I'm going to begin by creating a new file in the components folder hookCounterTwo.js, within the file I'm going to create a functional component, next we use the useState() hook to create a state variable and the corresponding setter function.
Table of Contents

Initialise state variable in functional component

  const initialCount = 0
const [count, setCount] = useState(initialCount)

The count comma set count is equal to you state with a default value of initial count, now we can add the JSX count is going to be count itself and then we add the three buttons to reset increment and decrement the count value.

Added Reset, Increment and Decrement buttons in the JSX

import React, { useState } from 'react'

function HookCounterTwo() {
const initialCount = 0
const [count, setCount] = useState(initialCount)

return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</>
)
}

export default HookCounterTwo

Reset Button - For the reset button on click is going to be an arrow function call setCount passing an initialCount as the argument this will set the count value back to zero.

Increment Button - next we have the increment button on click call setCount and the argument is going to be count + 1 

Decrement Button - similarly, for decrement on click is going to be setCount(count- 1).


We can now include the component and App component HookCounterTwo.js, if you save the files and take a look at the browser we must have the new counter I click on increment and the count increases click on decrement the count decreases and clicking on reset will set it back to zero.

Now you might be wondering what is so special about this example, how does it differ from what we have seen in the previous article?

Well, here's the thing the current implementation and the way we incremented count value in the previous article both are unsafe.

Although it looks like it is working but it is not the right way to change the count value.


let me explain you why with a very unlikely piece of code you would implement.

I'm going to add another button that increments the count by a value of 5  and text is going to be increment 5, then on click let's call a function called incrementFive()

Added incrementFive method and button

import 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 => count + 1)
}
}
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={incrementFive}>Increment 5</button>
</>
)
}

export default HookCounterTwo

Now let's define this increment 5 is an arrow function and within the body for that I is equal to 0 I less than 5 I plus plus and we call set count incrementing it by 1 every time so rather than incrementing it by a value of five and simply looping it five times and incrementing by one every time.

If we go back to the browser and test this out click on incrementFive, you can see that the count is still incremented by only one.

The set count method is reading a stale value of the count state variable, to overcome this we need to use the second form of the set count function basically instead of passing in a value of the new state variable we pass in a function that has access to the old state value.

So set count is going to accept a function that has access to the old count so previousCount is going to be the argument and the function body previousCount+ one.

Reading previous state variable value from argument

import React, { useState } from 'react'

function HookCounterTwo() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const incrementFive = () => {
for (let i = 0; i < 5; i++) {
setCount(prevCount => prevCount + 1)
}
}
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={incrementFive}>Increment 5</button>
</>
)
}

export default HookCounterTwo

So we pass in a function that has access to the old value and increment that by one, now if we go back to the browser and test it out increment five. You can see that the value increased by five.

So anytime you need to update 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.

Let me make the changes for increment and decrement buttons as well to copy this paste it here and here and change plus to minus. If you now take a look at the browser you should see the counter still working perfectly fine.

Example, Final code

import React, { useState } from 'react'

function HookCounterTwo() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const incrementFive = () => {
for (let i = 0; i < 5; i++) {
setCount(prevCount => prevCount + 1)
}
}
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment
</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>
Decrement
</button>
<button onClick={incrementFive}>Increment 5</button>
</>
)
}

export default HookCounterTwo

All right that is the second example, when you have to update state based on the previous state value pass in a function to the state setter.

Let me also quickly show you the class component equivalent 

How to use previous state in Class components?

import React, { Component } from 'react'

class ClassCounterTwo extends Component {
constructor(props) {
super(props)

this.state = {
count: 0
}
}

incrementCount = () => {
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
}

render() {
return (
<div>
<button onClick={this.incrementCount}>Count {this.state.count}</button>
</div>
)
}
}

export default ClassCounterTwo

If you're new to class components don't worry about this now we are not quite done with all the details of useState() hook, let's take a look at another example in the next article. 


To learn more about UseState hook, please go through below links

React hooks introduction

React useState hook

How to use an object as a state variable with the useState hook

How to use the state hook when the state variable is an array?