'useState() undefined in React just after set
I have a problem and I do not undersatnd why the hook return undefined :
import React, { useEffect, useState } from 'react';
function App(){
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable) // return undefined
},[]);
return (
<div>
</div>
);
}
export default App;
How can I do in order to set directly a new value for the globalVariable ?
Solution 1:[1]
As mentioned by other members, setState runs asynchronously in a queue-based system. This means that if you want to perform a state update, this update will be piled up on the queue. However, it does not return a Promise, so you can't use a .then
or a wait
. Because of this, we need to be aware of possible problems that might occur while manipulating states in React.
Say we have:
// PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
console.log(count); // returns 0
}
return (
<button onClick={increment}>Increment<\button>
)
}
That is basically the same problem you have. Since setCount is asynchronous, the console.log
will return the value before the updates (which is 0 here). In your case, it will return undefined
because you didn't pass any initial state in the useState hook. Other thing you did different was to fire the state change from inside the useEffect
hook, but that doesn't matter for the problem at hand.
To fix this, a good practice is to store the new state in another variable, as follows:
// SOLUTION TO PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
const newCountValue = count + 1;
setCount(newCountValue);
console.log(newCountValue); // returns 1
}
return (
<button onClick={increment}>Increment<\button>
)
}
This basically answers your question.
But, there are still other scenarios where state updates might not behave as one would expect. It is important to be aware of these scenarios to avoid possible bugs. For example, let's assume that we are updating a given state multiple times in a row, like:
// PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
console.log(count); // returns 0, but the final state will be 1 (not 3)
}
return (
<button onClick={increment}>Increment<\button>
)
}
Here, console.log
still returns 0 for the same reason described in Problem #1. But, if you are using the count
state somewhere in your app, you will see that the final value after the onClick
event is 1, not 3.
Here is how you can fix this:
// SOLUTION TO PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
console.log(count); // returns 0, but the final state will be 3
}
return (
<button onClick={increment}>Increment<\button>
)
}
By passing a function to setState, the new state is computed using the previous value on the queue and not the initial one.
As a final example, say we are calling a function right after trying to mutate our state, but this function depends on the state value. Since the operation is asynchronous, that function will use the "old" state value, just like in Problem #1:
// PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
onCountChange() {
console.log(count); // returns 0;
}
function increment() {
setCount(count + 1);
onCountChange();
}
return (
<button onClick={increment}>Increment<\button>
)
}
Notice in this problem that we have a function onCountChange
that depends on an external variable (count
). By external I mean that this variable was not declared inside the scope of onCountChange
. They are actually siblings (both lie inside the Button
scope). Now, if you are familiar with the concept of "pure functions", "idempotent" and "side effects", you will realize that what we have here is a side-effect issue -- we want something to happen after a state change.
Roughly speaking, whenever you need to handle some side-effects in React, this is done via the useEffect hook. So, to fix our 3rd problem, we can simply do:
// SOLUTION TO PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // returns 0;
}, [count]);
function increment() {
setCount(count + 1);
}
return (
<button onClick={increment}>Increment<\button>
)
}
In the solution above, we moved the content of the onCountChange
to useEffect and we added count
as a dependency, so whenever it changes, the effect will be triggered.
Solution 2:[2]
Because state only has new value when component re-render. So just put console.log(globalVariable)
outside useEffect like this:
useEffect(() => {
const test = 5;
console.log(test); // return 5
setGlobalVariable(test);
}, []);
console.log(globalVariable);
Solution 3:[3]
Two things:
useState
has an initial value, defaulted to undefined.
const [globalVariable, setGlobalVariable] = useState(123); // initial value set to 123
setGlobalVariable
is not a synchronous operation. Changing it will not mutateglobalVariable
immediately. The value in scope will remain the same until the next render phase.
Solution 4:[4]
This is because component is not re-rendering. You need to keep the value between the renders and for that you need to change the variable to a state using useState. Check below code for better understanding:
import React, { useEffect, useState } from "react";
function Test() {
const [globalVariable, setGlobalVariable] = useState();
const [test, setTest] = useState(5);
useEffect(() => {
// const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
console.log(globalVariable); // return undefined
console.log(test); // return 5
}, []);
return <div></div>;
}
export default Test;
Solution 5:[5]
This is because React state update is queue based system. It won't update immediately. But if you render a global variable value, you get the required one.
If you have same requirement in class-based component, then you need to pass callback function to setState. It will execute as soon as state is updated.
this.setState(stateupdate, callback)
Once the state is updated, the callback function will execute with the latest state.
const StackOverflow = () => {
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable)
},[]);
return (
<Container maxWidth="lg" style={{paddingTop:"5%"}}>
<div>
<h1>StackOverflow - {globalVariable}</h1>
</div>
</Container>
)
}
Solution 6:[6]
that's because the setGlobalVariable(test); takes some time, and you are trying to do it in the useEffect, which runs only once, you should use a setTimeOut function to take some time to show the state, try this and let me know is it working or not.
const [globalVariable, setGlobalVariable] = useState();
useEffect(() => {
const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
setTimeout(() => {
console.log(globalVariable);
}, 2000);
}, []);
Solution 7:[7]
You cannot console.log(globalVariable)
right after setState and expect to see the updated variable at the moment because setState
is asynchronous.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Viet |
Solution 3 | Federkun |
Solution 4 | |
Solution 5 | B.Anup |
Solution 6 | Dawood Ahmad |
Solution 7 |