1

I'm building a simple app in pure Reactjs. Component I'm having problems is a component that is supposed to render a number of buttons by mapping an array that has previously been populated by fetching some data from an external API. This array is populated within a class method and the results are eventually copied onto another array which is part of the state of the component

When I console.log the contents of the array on the render method of my component, everything looks fine. However if I try to print a specific element by its index, "undefined" is printed on the console. As a result the map function does not render all the desired buttons.

I have managed to find different documentation around the way I'm populating the array but none of the articles so far suggest that I'm doing anything fundamentally wrong. At least not that I can see.

State stores an empty array to start with and within the componentWillMount method an API gets called that fetches data and updates the array as per the below:

this.state = {
      resources: []
}

getAPIavaiableResources(api_resource) {
    let buttonsArray = []
    fetch(api_resource)
      .then(response => response.json())
      .then(data => {
        for (let i in data) {
          buttonsArray.push({id: i, name: i, url: data[i]})
        }
      }).catch(error => console.log(error))

    this.setState({resources: buttonsArray})
}

componentWillMount() {
    this.getAPIavaiableResources(ROOT_RESOURCE)
}

render() {
    const { resources } = this.state;
    console.log(resources)
    console.log(resources[0])

    return (
      <div className="buttons-wrapper">
        {
          resources.map(resource => {
            return <Button
                      key={resource.id}
                      text={resource.name} 
                      onClick={this.handleClick} 
                    />
          })
        }
      </div>
    )
}

This is what gets printed onto the console on the render method.

[]
0: {id: "people", name: "people", url: "https://swapi.co/api/people/"}
1: {id: "planets", name: "planets", url: "https://swapi.co/api/planets/"}
2: {id: "films", name: "films", url: "https://swapi.co/api/films/"}
3: {id: "species", name: "species", url: "https://swapi.co/api/species/"}
4: {id: "vehicles", name: "vehicles", url: "https://swapi.co/api/vehicles/"}
5: {id: "starships", name: "starships", url: "https://swapi.co/api/starships/"}
length: 6
__proto__: Array(0)

Can anyone see what I'm doing wrong? I'm pushing an object because I do want an array of objects albeit arrays in Javascript are objects too. Any help would be appreciated.

4
  • resources[0].map would fix for you in return statement instead of using resources.map Commented May 17, 2019 at 14:30
  • Show us the api response Commented May 17, 2019 at 14:35
  • In your fetch call where you have .then(data => { }) inside there console.log() out what data is. It's probably something like needing it to be data.data Commented May 17, 2019 at 14:35
  • This is the object that the API returns just for further reference: { people: "swapi.co/api/people", planets: "swapi.co/api/planets", films: "swapi.co/api/films", species: "swapi.co/api/species", vehicles: "swapi.co/api/vehicles", starships: "swapi.co/api/starships" } proto: Object Commented May 17, 2019 at 14:47

2 Answers 2

1

Your current implementation is setting state before you have the data, and then mutating state once the api call comes back. React can't tell when you mutate things, and thus doesn't know to rerender. Only when you call setState (or when it receives new props) does it know to rerender.

Instead, wait until you have the data and only then call setState with the populated array.

getAPIavaiableResources(api_resource) {
  fetch(api_resource)
    .then(response => response.json())
    .then(data => {
      let buttonsArray = []
      for (let i in data) {
        buttonsArray.push({id: i, name: i, url: data[i]})
      }
      this.setState({resources: buttonsArray})
    }).catch(error => console.log(error))
}

componentDidMount() {
    this.getAPIavaiableResources(ROOT_RESOURCE)
}

The above example also updates the code to use componentDidMount instead of componentWillMount. componentWillMount is deprecated, and wasn't intended for this sort of case anyway.

Sign up to request clarification or add additional context in comments.

2 Comments

Changing the state before the Promise was returning a success was the main problem indeed. I've also modified my code to include all on componentDidMount and a conditional rendering logic as both Mehmet and Nicholas suggested.
I wish I could mark both answers as the solution to my problem as both would indeed solve the problem.
0

Currently you are setting the state without waiting for the promise to be resolved. In order to do that, move this.setState({resources: buttonsArray}) after for loop.

In addition, you can render the component conditionally until the you get what you want from the remote resource by doing:

render () {
  const { resources } = this.state;
  return resources.length
    ? (
      <div>Your content...</div>
    )
    : null // or some loader
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.