At some point or another, when you are writing your JavaScript code with React, you will come across the situation where you need to use the useState’s setState from within useEffect.
When I say setState I am talking about one of the values returned from the react hook useState.
useState will return the current stateValue and then a setter function, which we will call setState, that you can use to update the state value.
The values are returned as an array and the setter function, setState, is returned as the second index of the react hook, useState
.
It’s just easier to say setState.
So if you are in the situation where you need to do this, you might be wondering how you go about it when you have to consider useEffect, the dependency array, stale variables, and if your state variable will still be up-to-date by the time your useEffect
function gets run.
Well, I know that in the past I have personally wondered this and it is an easy thing to forget so I am writing this post to document everything you need to know about the situation of using setState from within useEffect.
Let’s dive right in.
Dealing with setState from within useEffect with the dependency array
Firstly, let’s understand what the dependency array is before we answer this question.
The dependency array of useEffect, to put it simply, is the way in which React decides when to run our useEffect function, and what variables need to be refreshed each time it does get run.
Under the hood, what is happening is React is comparing each one of the dependencies in the array to the previous version of it from the last render.
If the all versions are identical, then the variables will not be refreshed and the function will not be re-run.
However, if any of the dependencies change between renders, then React will detect this, and then refresh all the variables and then run your function.
Thankfully, using setState from within the useEffect dependency array couldn’t be simpler.
React actually guarantees that the setState setter function will not change between renders so you don’t actually need to include it in the dependency array.
Here is a snippet from the official react docs:
React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.
TLDR, do I need to include the useState setter function, setState within my useEffect dependency array?
No, you can safely use the setState setter within a useEffect without including it in the dependency array.
You can find the official react docs for this here
Stale variables within useEffect
Stale variables within useEffect can cause some strange, difficult to debug, bugs.
Now either you have probably come across this before, or you have had warnings from eslint about stale variables.
All this really means is that the variables that are being used in your react hook (useEffect) are old, and out of date.
You need to update them with the latest version of the variables so that the function runs as expected and you get the desired outcome you were looking for.
The solution to this circles us back to the dependency array, React already solves this for us as long as we always pass in any variables that are subject to change within the dependency array.
Here are some other articles that cover this topic in a bit more detail:
- Using componentDidMount in react hooks
- What is the difference between useMemo and useCallback?
- React.memo vs useMemo
But for the purposes of this post, you just need to remember that it is important to pass dependencies into the dependency array if they change at any point.
Here is a quick example of how to do this:
useEffect(() => {
console.log(a + b)
}, [a, b])
Up-to-date state and setState from within useEffect
Okay, we are almost there, so what about when it comes to using the setState from within useEffect and ensuring that all the dependencies are up to date with no state variables being out of date.
This issue becomes more and more apparent with the more useState
hooks you are using at any one time.
For example, what happens when you need both state a, and state b from within a useEffect hook but both are being set in a function similar to the following one:
export default function MyComponent() {
const [stateA, setStateA] = useState(1)
const [stateB, setStateB] = useState(10)
const handleClick (e) => {
e.preventDefault()
setStateA(2)
setStateB(20)
}
return (
<button onClick={handleClick}> Click me </button>
)
}
Well in this example, React, once again, comes to the rescue and will batch these setState functions causing only one re-render, meaning both should be the most up to date version when the useEffect function gets run.
However when you start thinking about async calls and so on you can see that there may be a problem here.
Luckily, there is a way in which you can ensure you are always using the most up-to-date version of your state.
Let’s take a look at the following code to show this in more detail:
export default function MyComponent() {
const [stateA, setStateA] = useState(1)
const [stateB, setStateB] = useState(10)
useEffect(() => {
setStateA(stateA * 10)
setStateB(stateB * 10)
}, [stateA, stateB])
const handleClick (e) => {
e.preventDefault()
setStateA(2)
setStateB(20)
}
return (
<button onClick={handleClick}> Click me </button>
)
}
Now as you can see, this is how we have handled this so far.
However, there is a better way to do this that will let React worry about the latest, most up to date version of the state so we don’t have to.
The setState functions actually accept a callback that returns the next state value.
The callback will be provided with the current, most up-to-date version of your state, which means you can always be certain that you have the most up to date version.
Here is an example using this:
export default function MyComponent() {
const [stateA, setStateA] = useState(1)
const [stateB, setStateB] = useState(10)
useEffect(() => {
setStateA(a => a * 10)
setStateB(b => b * 10)
}, [stateA, stateB])
const handleClick (e) => {
e.preventDefault()
setStateA(2)
setStateB(20)
}
return (
<button onClick={handleClick}> Click me </button>
)
}
As a rule of thumb, if you ever have to manipulate the state for a counter or any kind of updating (rather than replacing) then it will most likely be better to use the callback version.
TLDR of how to use setState from within useEffect with dependencies.
- All variables subject to change that you use in your useEffect function should be included within the useEffect dependency array.
- You do not need to include the setter function
setState
in your dependency array because React guarantees that it will not change. - Stale variables are caused when you do not include variables that change in the dependency array that are used in your useEffect function.
- For the most part when updating a state variable it is safer to use the callback that provides the latest state that it is to directly use the state variable
I hope this article has helped clarify and solve any issues you have when using the react hooks useState and useEffect together.
Will