Rihards Repels
Rihards Repels

Reputation: 45

ReactJS - JSON objects in arrays

I am having a little problem and can't seem to understand how to fix it. So I am trying to create a pokemon app using pokeapi. The first problem is that I can't get my desired objects to display. For example I want to display {pokemon.abilities[0].ability}, but it always shows Cannot read property '0' of undefined but just {pokemon.name} or {pokemon.weight} seems to work. So the problem appears when there is more than 1 object in an array.

import React, {Component} from 'react';

export default class PokemonDetail extends Component{
    constructor(props){
        super(props);
        this.state = {
            pokemon: [],
        };
    }
    componentWillMount(){
            const { match: { params } } = this.props;
        fetch(`https://pokeapi.co/api/v2/pokemon/${params.id}/`)
            .then(res=>res.json())
            .then(pokemon=>{
                this.setState({
                    pokemon
                });
            });
    }
    render(){
        console.log(this.state.pokemon.abilities[0]);
        const { match: { params } } = this.props;
        const {pokemon} = this.state;
        return (
            <div>
                {pokemon.abilities[0].ability}
                <img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
            </div>
        );
    }
}

And also some time ago I added the router to my app, so I could pass id to other components, but the thing is I want to display pokemonlist and pokemondetail in a single page, and when you click pokemon in list it fetches the info from pokeapi and display it in pokemondetail component. Hope it makes sense.

import React, { Component } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom';

import './styles/App.css';

import PokemonList from './PokemonList';
import PokemonDetail from './PokemonDetail';

export default class App extends Component{
    render(){
        return <div className="App">
            <Router>
            <div>
                <Route exact path="/" component={PokemonList}/>
                <Route path="/details/:id" render={(props) => (<PokemonDetail {...props} />)}/>
            </div>
            </Router>
        </div>;
    }
}

Upvotes: 0

Views: 838

Answers (2)

mkb
mkb

Reputation: 25516

In case componentWillMount(), An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once.

To handle this we need to set initial state which you have done in constructor but it's not correct. you need to provide default values for the abilities which is an empty array. So change it to

    this.state = {
      pokemon: {
        abilities: []
      }
    }

And then inside render method before rendering you need to verify that it's not empty

     render() {
       return (
         (this.state.pokemon.abilities[0]) ?
           <div>
             {console.log(this.state.pokemon.abilities[0].ability)}
             <img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png`} />
           </div> :
           null
         );
       }

Upvotes: 1

blaz
blaz

Reputation: 4078

It is common in React that you always need to safe-check for existence of data before rendering, especially when dealing with API data. At the time your component is rendered, the state is still empty. Thus this.state.pokemon.abilities is undefined, which leads to the error. this.state.pokemon.name and this.state.pokemon.weight manage to escape same fate because you expect them to be string and number, and don't dig in further. If you log them along with abilities, they will be both undefined at first.

I believe you think the component will wait for data coming from componentWillMount before being rendered, but sadly that's not the case. The component will not wait for the API response, so what you should do is avoid accessing this.state.pokemon before the data is ready

render(){
    const { match: { params } } = this.props;
    const {pokemon} = this.state;
    return (
        <div>
            {!!pokemon.abilities && pokemon.abilities[0].ability}
            <img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
        </div>
    );
}

Upvotes: 1

Related Questions