Home > database >  React: Why is my variable only defined on the first render but undefined on subsequent renders?
React: Why is my variable only defined on the first render but undefined on subsequent renders?

Time:10-01

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]);
  • Related