2

For some reason I'm unable to access the "question" key within the results array. Could someone help please? This is just a side project I've started and have been tearing my hair out for hours trying to get this first basic part to work. I've only been using react and API's for about a week now and I'm super inexperienced, so any help would be appreciated.

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {

  constructor() {
    super()
    this.state = {
      questionList: []

    }
  }

  componentDidMount() {
    fetch('https://opentdb.com/api.php?amount=10')
      .then(resp => resp.json())
      .then(resp => this.setState({ questionList: resp }))
  }

  render() {
    return (
      <div>{this.state.questionList.results[0].question}</div>

    )
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
)

and here's the API data -

{
  "response_code": 0,
  "results": [
    {
      "category": "Entertainment: Film",
      "type": "multiple",
      "difficulty": "medium",
      "question": "Which of the following James Bond villains is not affiliated with the SPECTRE organization?",
      "correct_answer": "Auric Goldfinger",
      "incorrect_answers": [
        "Dr. Julius No",
        "Rosa Klebb",
        "Emilio Largo"
      ]
    },
    {
      "category": "Geography",
      "type": "multiple",
      "difficulty": "hard",
      "question": "The mountainous Khyber Pass connects which of the two following countries?",
      "correct_answer": "Afghanistan and Pakistan",
      "incorrect_answers": [
        "India and Nepal",
        "Pakistan and India",
        "Tajikistan and Kyrgyzstan"
      ]
    },
    {
      "category": "Entertainment: Video Games",
      "type": "multiple",
      "difficulty": "medium",
      "question": "In Team Fortress 2, which class wields a shotgun?",
      "correct_answer": "Everyone Listed",
      "incorrect_answers": [
        "Heavy",
        "Soldier",
        "Engineer"
      ]
    },
    {
      "category": "Entertainment: Video Games",
      "type": "multiple",
      "difficulty": "easy",
      "question": "Who is the leader of Team Mystic in Pok&eacute;mon Go?",
      "correct_answer": "Blanche",
      "incorrect_answers": [
        "Candela",
        "Spark",
        "Willow"
      ]
    },
    {
      "category": "Science & Nature",
      "type": "multiple",
      "difficulty": "easy",
      "question": "The medical term for the belly button is which of the following?",
      "correct_answer": "Umbilicus",
      "incorrect_answers": [
        "Nevus",
        "Nares",
        "Paxillus"
      ]
    },
    {
      "category": "Entertainment: Cartoon & Animations",
      "type": "multiple",
      "difficulty": "easy",
      "question": "What is lost in Hawaiian and is also the name of a little girl in a 2002 film which features a alien named &quot;Stitch&quot;?",
      "correct_answer": "Lilo",
      "incorrect_answers": [
        "Lolo",
        "Lucy",
        "Lulu"
      ]
    },
    {
      "category": "Entertainment: Cartoon & Animations",
      "type": "multiple",
      "difficulty": "hard",
      "question": "In &quot;Gravity Falls&quot;, what does Quentin Trembley do when he is driven out from the White House?",
      "correct_answer": "Eat a salamander and jump out the window.",
      "incorrect_answers": [
        "Leave in peace.",
        "Jump out the window.",
        "Release 1,000 captive salamanders into the white house."
      ]
    },
    {
      "category": "Entertainment: Television",
      "type": "multiple",
      "difficulty": "hard",
      "question": "Who was the winner of &quot;Big Brother&quot; Season 10?",
      "correct_answer": "Dan Gheesling",
      "incorrect_answers": [
        "Bryce Kranyik",
        "Ryan Sutfin",
        "Chris Mundorf"
      ]
    },
    {
      "category": "General Knowledge",
      "type": "multiple",
      "difficulty": "easy",
      "question": "Terry Gilliam was an animator that worked with which British comedy group?",
      "correct_answer": "Monty Python",
      "incorrect_answers": [
        "The Goodies&lrm;",
        "The League of Gentlemen&lrm;",
        "The Penny Dreadfuls"
      ]
    },
    {
      "category": "Entertainment: Video Games",
      "type": "multiple",
      "difficulty": "easy",
      "question": "In Counter-Strike: Global Offensive, what&#039;s the rarity of discontinued skins called?",
      "correct_answer": "Contraband",
      "incorrect_answers": [
        "Discontinued",
        "Diminshed",
        "Limited"
      ]
    }
  ]
}

Thanks!

Andre
  • 65
  • 2
  • 5
  • Log the resp in the second callback before setting state. https://developer.mozilla.org/en-US/docs/Web/API/Body/json Is it failing to render the first time tho? you need to check the key exist before trying to render, because by the time the first render happens the data hasn't come back from the server and the values are not available to show. – Dvid Silva Nov 18 '19 at 20:26
  • Do you even get a result back from the API? In other words, does `console.log(resp.json());` print something? Maybe you need to rename the second `resp` to something else, e.g. `body`. – oemera Nov 18 '19 at 20:28
  • 1
    Possible duplicate of [Why calling react setState method doesn't mutate the state immediately?](https://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately) – Emile Bergeron Nov 18 '19 at 20:49
  • `fetch` and `setState` are async. Meaning that `render` gets called before the state is populated with the data. This has been addressed a number of times already on Stack Overflow. – Emile Bergeron Nov 18 '19 at 20:52

4 Answers4

1

This will surely fail, since you are calling an asynchronous action, but your render relies on that explicitly:

{this.state.questionList.results[0].question}

You are probably receiving Cannot read property 'question' of undefined

or

Cannot read property '0' of undefined

The most basic solution would be to check if the questionList actually has some data inside.

Note: questionList in an array, not an object.

render() {
   if (this.state.questionList.length === 0) {
      return null;
   }

   return (
     <div>{this.state.questionList[0].results[0].question}</div>
   );
 }

However the best, but the longest solution would be check step-by-step that questionList has a correct structure:

{this.state.questionList[0] 
    && this.state.questionList.results
    && this.state.questionList.results[0] 
    && this.state.questionList.results[0].question}
kind user
  • 40,029
  • 7
  • 67
  • 77
1

As other's have noted, your API call will take a period of time, and your App will render before it is done, so your data isn't there yet when it goes to render. You're basically trying to get data that doesn't exist yet.

I like to have a loading flag on my state to show loading stuff, as well an an error flag for any errors that come in.

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {

  constructor() {
    super()
    this.state = {
      questionList: {
        isLoading: true,
        error: null,
        data: null,
      }

    }
  }

  componentDidMount() {
    fetch('https://opentdb.com/api.php?amount=10')
      .then(resp => resp.json())
      .then(resp => this.setState(
        {questionList: 
         {data: resp, isLoading: false, error: null} 
       }))
      .catch(e => this.setState({questionList: {data: null, error: e, data: null}}))
  }

  render() {
    if (this.state.questionList.isLoading)
      return <Loading />
    if (this.state.questionList.error)
     return <Error error={this.state.questionList.error} />
    return (
      <div>{this.state.questionList.data.results[0].question}</div>

    )
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
)

You can and should be a little smarter with your checks, and data fixing, etc, etc... but that is the gist of how I deal with it.

I also recommend wrapping up your API calls in a module. So you could say api.questions.getList().then(...) instead of having the fetch in your components, and any data conversions, or other ugly stuff can be put in those modules. It isn't really related to your question (and would probably be overkill for this anyways), but since you seem to new to react I'll throw that out there for you.

Adam LeBlanc
  • 932
  • 7
  • 21
0

use console.log in render()

and you will see that your state is empty, because you call async function.

There is:

  1. Api call - it can take 1 sec, 10 sec, 20 sec etc...
  2. call render() - your state is empty because you need to wait for API response
  3. you will get error...

You can add some code, eg:

  render() {
    if (this.state.questionList.length === 0) {
        return '<div>Loading...</div>';
    }

    return (
      <div>{this.state.questionList.results[0].question}</div>

    )
  }
taro
  • 63
  • 6
-2

I am not sure that your results are back to the component by the time it renders. I think you need to run a check to see if the results are there. Can you try something like this and we can go from here?

{this.state.questionList.results.length > 1 && this.state.questionList.results[0].question}
lakerskill
  • 1,019
  • 1
  • 11
  • 24