When I run the following code, a strange issue occurs :
import {useState, useEffect} from 'react'
function App() {
const [data, setData] = useState([]);
useEffect(()=>{
{console.log("useEffect start")} // Step 1 after useEffect start
const getApiData = async () => {
console.log("async function start") // Step 2
const apiData = await fetch("https://api.chucknorris.io/jokes/random").then(res=>res.json());
console.log("get API data") // Step 3
setData(data.concat(apiData));
console.log("after setData",data)} // Step 6
getApiData();
},[])
console.log("outside useEffect",data); // Step 4
return (<>
<li>{data.map(i => <li>{i.value}</li>)}</li>
{console.log("inside return")} // Step 5
</>)}
export default App
Here are the sequence results from the console:
outside useEffect []
inside return
useEffect start
async function start
get API data
outside useEffect [{…}] // Since useState(setData) is asynchronous,
// why this step can get the return data value before setData is completed?
inside return
after setData []
What I don’t understand is, if useState(setData) is asynchronous, why can I get the return value of data in Step 4? Since I can get the return value of data in Step 4, it means that useState(setData) has already completed before Step 4. If so, why does Step 6 come after Step 4? If useState(setData) has already been completed, then according to the sequence of the code lines, the next execution should be console.log("after setData", data)
. Do I miss something here?
Stack Snippet:
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<script type="text/babel" data-presets="es2017,react,stage-3">
const { useState, useEffect } = React;
function App() {
const [data, setData] = useState([]);
useEffect(()=>{
{console.log("useEffect start")} // Step 1 after useEffect start
const getApiData = async () => {
console.log("async function start") // Step 2
const apiData = await fetch("https://api.chucknorris.io/jokes/random").then(res=>res.json());
console.log("get API data") // Step 3
setData(data.concat(apiData));
console.log("after setData",data)} // Step 6
getApiData();
},[])
console.log("outside useEffect",data); // Step 4
return (<>
<li>{data.map(i => <li>{i.value}</li>)}</li>
{console.log("inside return")} {/* Step 5*/}
</>)}
const root = ReactDOM.render(<App />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/[email protected]/runtime.js"></script>
<script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
3
Answers
setData update all List And then he did it again so you can again see outside useEffect [{…}]
This is because React will run twice with useEffect in development mode.
Try see result after run build.
Or remove <React.StrictMode> component to disable the default behaviour.
Result:
I will try to address directly the part which is most confusing.
Why do we get the below output?
Isn’t setState asynchronous?
setState batches state updates and delays rendering in most cases i.e., when the updates are from event handlers, but not from promises.
Here is the github isuse:
This is one of the useful comments:
To put it more clearly, get this example:
In React 17 and before,
will cause synchronous renders. Once
setCounter
sets the state to 2, it will cause a render. After that it will return and setState to 3, and cause another render.Why
setCounter(2)
, causes a render synchronously is because:I have picked up the explanation from a great blog blog
Most of the above I have mentioned applies to React 17. The above behaviour is different in React 18 (and above). React 18 is even more optimized for fewer renders :D. Hence TJ sees a different output.