React noob here.
I am having a hard time understanding how the useEffect and useState hooks work.
My App fetches data which upon retrieval needs to be rendered.
I am using the then() function to ensure that the variables are defined in the correct order. I have also tried using multiple useEffect() functions to guarantee the proper chronology. However, for whatever reason, my current
variable is undefined. It is only defined on the first render.
import React, { useEffect, useState } from 'react'
export default function Answers() {
const [questions, setQuestions] = useState([])
const [current, setCurrent] = useState()
useEffect(() => {
fetch('http://localhost:8000/questions')
.then(res => res.json())
.then(data => setQuestions(data))
.then(setCurrent(questions[0]))
}, [])
return (
<div>
<ul>
{console.log(current['answers'])}
{current['answers'].map(item => <li>{item}</li>)}
</ul>
</div>
)
}
Here is the data being fetched:
{
"questions": [
{
"id": 1,
"question": "to work",
"answers": ["yapmak", "gitmek", "çalışmak", "gelmek"],
"correct": 3
},
{
"id": 2,
"question": "to know",
"answers": ["gitmek", "bilmek", "çalışmak", "gelmek"],
"correct": 2
},
{
"id": 3,
"question": "to want",
"answers": ["istemek", "gitmek", "çalışmak", "konuşmak"],
"correct": 1
}
]
}
CodePudding user response:
Code inside the useEffect
hook is not doing what you probably expect.
First of all
.then(setCurrent(questions[0]))
will immediately call the function. You probably meant to write:
.then(() => setCurrent(questions[0]))
but that's still not correct because questions[0]
will be undefined
.
To understand why it will be undefined
, you need to know two things about state update in React:
- It is updated asynchronously
- It is constant within a particular render of a component. Component can't see the updated state until it re-renders
In light of the above two points, we cannot use questions
in the third then()
method's callback function.
What you need to do is return the data
from the 2nd then
method's callback function as shown below:
fetch('http://localhost:8000/questions')
.then(res => res.json())
.then(data => {
setQuestions(data);
return data;
})
.then(data => setCurrent(data[0]))
Returning data
from the callback of second then()
method's callback function is needed to pass the data
from the callback function of second then()
to the callback function of third then()
method.
Remove unnecessary "then()" method call
You don't really need the last then()
method to call setCurrent
- you can call it inside the second then()
method.
fetch('http://localhost:8000/questions')
.then(res => res.json())
.then(data => {
setQuestions(data);
setCurrent(data[0]);
})
Using the useEffect hook
Alternative option is to update current
state inside another useEffect
hook that executes whenever questions
state is updated.
(following useEffect
hook will execute after questions
state has been updated and the component has re-rendered as a result of that state update)
useEffect(() => {
setCurrent(questions[0]);
}, [questions]);
If you take this approach, then the first useEffect
could be re-written as:
useEffect(() => {
fetch('http://localhost:8000/questions')
.then(res => res.json())
.then(data => setQuestions(data))
.catch(error => { /* handle error */ });
}, []);
Note: Not related to your problem but you shouldn't omit the catch()
method call to catch and handle any errors during the HTTP request.
CodePudding user response:
The problem is in the way you set the current
value, when you're calling setCurrent
, the questions
state is not ready yet. Try adding an additional useEffect
hook that reacts to changes in questions
:
useEffect(() => {
fetch('http://localhost:8000/questions')
.then(res => res.json())
.then(data => setQuestions(data))
}, []);
useEffect(() => {
if (questions) {
setCurrent(questions[0]);
}
}, [questions]);