Reputation: 353
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.
Upvotes: 1
Views: 2149
Reputation: 616
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
}
Upvotes: 0
Reputation: 84912
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.
Upvotes: 1