Paul Miranda
Paul Miranda

Reputation: 748

TypeError: Cannot read property of undefined using React

I'm new in React and I'm doing a little app with PokeAPI. I have a component called PokemonDetail in which I want to show the details of a pokemon, but the app throws me the next error

TypeError: Cannot read property 'front_default' of undefined

my component looks like this:

import React from "react";

const PokemonDetail = ({ pokemon }) => {
  return (
    <div>
      <div className="text-center">{pokemon.name}</div>
      <img src={pokemon.sprites.front_default} alt={pokemon.name} />
      {pokemon.id}
    </div>
  );
};

export default PokemonDetail;

And the App component from which the PokemonDetail recive the prop of pokemon looks like this:

import React from "react";
import PokeAPI from "../apis/PokeAPI";
import SearchBar from "./SearchBar";
import PokemonDetail from "./PokemonDetail";

class App extends React.Component {
  state = { pokemon: '' };

  onTermSubmit = async term => {
    try {
      const response = await PokeAPI.get(`pokemon/${term}`);
      this.setState({ pokemon: response.data });
      console.log(response);
    } catch (error) {
      console.log("No existe");
    }
  };

  render() {
    return (
      <div className="container">
        <div className="row mt-3">
          <div className="col">
            <SearchBar onFormSubmit={this.onTermSubmit} />
          </div>
        </div>
        <div className="row mt-3">
          <div className="col-9" />
          <div className="col-3">
            <PokemonDetail pokemon={this.state.pokemon} />
          </div>
        </div>
      </div>
    );
  }
}

export default App;

I don't understand why it throws me this error because only throws it with this and other properties of the json. With the name property works and wait until I send it some props, same with the id but no with the front_default property, which is a url of a image.

Upvotes: 0

Views: 2574

Answers (4)

bill.lee
bill.lee

Reputation: 2375

The initial state is { pokemon: '' }; pokemon is an empty string. PokemonDetail is referring to pokemon.sprites.front_default, but pokemon is initially a string and a string does not have a field called sprites.

If you are expecting pokemon to eventually become an object, you could initialize it to something that looks like an object:

state = { pokemon: { sprites: {front_default: '' }}};

Upvotes: 0

Doro
Doro

Reputation: 355

@ZHAOXIANLONG gave you the best solution (use a loading component until you receive data), but, if you do not use a loading component, you can use the get method from lodash library [1] in order to avoid a possible error.

import React from "react";
import _ from 'lodash';

const PokemonDetail = ({ pokemon }) => {
    const front_default = _.get(pokemon, 'sprites.front_default', 'DEFAULT_VALUE');
    const name = _.get(pokemon, 'name', 'DEFAULT_VALUE');

    return (
        <div>
            <div className="text-center">{pokemon.name}</div>
            <img src={pokemon.sprites.front_default} alt={pokemon.name} />
            {pokemon.id}
        </div>
    );
};

export default PokemonDetail;

where the third parameter ('DEFAULT_VALUE') is a default value that will be used if the lodash can not retrieve a value for your query.

PS: I advise you to use lodash even in @ZHAOXIANLONG solution if you know that your API Server can be changed.

[1] https://lodash.com/docs/4.17.11#get

Upvotes: 1

ZHAOXIANLONG
ZHAOXIANLONG

Reputation: 36

Because ajax is slower than react rendering, you can use a loading component before you get the data.

const PokemonDetail = ({ pokemon }) => {
  if(pokemon.sprites == undefined){
      return(
        <div>
            Loading...
        </div>
      );
  }
  return (
    <div>
      <div className="text-center">{pokemon.name}</div>
      <img src={pokemon.sprites.front_default} alt={pokemon.name} />
      {pokemon.id}
    </div>
  );
};

Upvotes: 2

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15708

Very likely just an AJAX issue, your component renders before it has time to complete your request to the API. Try adding an additional check before rendering the image.

import React from "react";

const PokemonDetail = ({ pokemon }) => {
  return (
    <div>
      <div className="text-center">{pokemon.name}</div>
       {pokemon.sprites ? (
        <img src={pokemon.sprites.front_default} alt={pokemon.name} />
        ) : ( 
          null
        )
       }
      {pokemon.id}
    </div>
  );
};

export default PokemonDetail;

Upvotes: 1

Related Questions