Search code examples
reactjsuse-statepokeapi

How do I solve this "undefined" (reading of "...")?


Ive been doing my best to get a handle on APIs and wanted to start with something that would be fun and keep my attention. I ended up going with PokeAPI, and its been cool so far. I made this along with a tutorial that started with class components, after making that functional (no pun intended), I went back and converted them to functional components. Im at the point of frustration and not understanding what Im doing wrong. So can anyone point out and explain in simple terms what I am doing wrong? And what could I do better as a beginner/ intermediate? At first I reached out on another platform for help, thinking it was me calling the API wrong somewhere. But Im starting to think it is the way Ive structured it in useState and how I am referencing it later with my HTML.

The error is: Uncaught TypeError: Cannot read properties of undefined (reading 'hp') at PkmnStats (PkmnStats.js:138:1)

PkmnStats

import {useState, useEffect} from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import loading from './loading.gif';


const PkmnStats = () => {

    const [isLoading, setIsLoading] = useState(true);
    const [pkmnStat, setPkmnStat] = useState([
        {
            name:'',
            index:'',
            imageUrl:'',
            types:[''],
            description:'',
            stats:{

                hp:'',
                attack:'',
                defense:'',
                speed:'',
                specialAttack:''        
            },
            height:'',
            weight:'',
            abilities:['']
        }

    ]);

    const getPkmnStats = async () => {

        const index = useParams;
        const name =  useParams;
        const pkmnUrl = `https://pokeapi.co/api/v2/pokemon/${index}/`
        const pkmnSpecies = `https://pokeapi.co/api/v2/pokemon-species/${index}/`

        try{
                const pkmnResponse =  await axios.get(pkmnUrl).then(() => {

                setIsLoading(true);
                const imageUrl = pkmnResponse.data.sprites.front_default;
                const species = pkmnResponse.data.species;
        
                let [ hp, attack, defense, speed, specialAttack, specialDefense] = '';
        
                pkmnResponse.data.stats.map((stat) =>  {
                    if(stat.stat.name === 'hp'){
                        hp = stat['base_stat'];
                    }
                    if(stat.stat.name === 'attack'){
                        attack = stat['base_stat'];
                    }
                    if(stat.stat.name === 'defense'){
                        defense = stat['base_stat'];
                    }
                    if(stat.stat.name === 'speed'){
                        speed = stat['base_stat'];
                    }
                    if(stat.stat.name === 'special-attack'){
                        specialAttack = stat['base_stat'];
                    }
                    if(stat.stat.name === 'special-defense'){
                        specialDefense = stat['base_stat'];
                    }
                  
    
                });
                const height = Math.round((pkmnResponse.data.height * 0.328084 + 0.0001) * 100)/100;
                const weight = Math.round((pkmnResponse.data.weight * 0.220462 + 0.0001) * 100)/100;
        
                const types  = pkmnResponse.data.types.map(types => {return types.type.name});

                const abilities = pkmnResponse.data.abilities.map(abilities => {
                return abilities.ability.name
                });
                // getPkmnDescription(pkmnSpecies);

                let description ='';

                const getDesc =  axios.get(pkmnSpecies).then(() => {

                    getDesc.data.flavor_text_entries.some(flavor => {
        
                    if (flavor.language.name === 'en') {
                        description = flavor.flavor_text;
                        return description;
                        }
                    });
                    }).catch(error => {
                        setIsLoading(false);
                        console.log("An error happened", error);
                    });
                
                
                    setPkmnStat({  index, name, imageUrl, types, stats: {hp, attack, defense, speed, specialAttack, specialDefense}, height, weight, abilities, description})

                }).catch(error => {
                    setIsLoading(false);
                    console.log("An error happened", error);
                });
        }
        catch(err){
            console.log(err)
            setPkmnStat(null)

        }
    }
  
    useEffect(() => {

        getPkmnStats();
        
    },[])


    return (
            <>
            {isLoading ? (
                <div className='mx-auto w-fit h-screen p-6  md:w-3/5'>
                    <div className='flex flex-col items-center justify-center md:flex-col lg:flex-row'>
                            <div className='flex flex-col items-center '>
                                <p className='text-5xl md:text-6xl'>{pkmnStat.name}</p>
                                <img className=" w-32 h-32 md:w-64 md:h-64 " src={pkmnStat.imageUrl} alt={pkmnStat.name} />
                                <div className='flex flex-row '>
                                    <div>{pkmnStat[0] ? pkmnStat[0] : null}</div>
                                    <div>{pkmnStat[1] ? pkmnStat[1] :null}</div>
                                </div>
                                <p>Height: {pkmnStat.height} ft.</p>
                                <p>Weight: {pkmnStat.weight} lbs.</p>
                            </div>
                            <div className='flex flex-col items-center '>
                                <p className='text-center my-5'>{pkmnStat.description} </p>
                                
                                <p>Abilities: {pkmnStat.abilities}</p>
                                <div className='flex flex-col items-center mt-4 '>
                                        <p> HP: {pkmnStat.stats.hp}</p>
                                        <p> Attack: {pkmnStat.stats.attack}</p>
                                        <p>Defense: {pkmnStat.stats.defense}</p>
                                        <p>Speed: {pkmnStat.stats.speed}</p>
                                        <p>Spec.Attack: {pkmnStat.stats.specialAttack}</p>
                                        <p>Spec.Defense: {pkmnStat.stats.specialDefense}</p>
                                </div>
                        </div>
                    </div>
                </div>
                ) : ( <img src={loading} className="mx-auto bg-platnium" alt="loading gif"/>) }
            </> 
    )
    
}
export default PkmnStats

Here is two more for reference:

PkmnCard

import loading from './loading.gif'
import {Link} from 'react-router-dom'

const PkmnCard = ({pokemon}) => {

    const index = pokemon.url.split('/')[pokemon.url.split('/').length - 2];
    const sprite =`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${index}.png`;
    
    return (
     
        <Link to={`/pokemon/${pokemon.name}/${index}`}>
            {pokemon ? (<div className='flex flex-col h-64  items-center p-10 rounded-xl  shadow-lg hover:shadow-2xl hover:border-stone-700 bg-white select-none '>
                <h3 className='text-center capitalize'>{pokemon.name}</h3>
                
                {pokemon.imageLoading ? (<img src={loading} className="mx-auto w-10 h-10" alt="loading gif"/>) :null}
                <img className='mx-auto' style={pokemon.tooManyRequests ? {display: "none"} : pokemon.imageLoading ? null : {display: "block"}} src={sprite} alt={pokemon.name} onLoad={() => ({imageLoading: false})} onError={ () => ({ tooManyRequests: true})} />
                {pokemon.tooManyRequests ? (<h6 className='mx-auto bg-red-600'> <span className='text-white'>Too Many Requests </span></h6>) :null }
                <h3 className="text-center">-No.{index}-</h3>
            
            </div>):  (<img src={loading} className="mx-auto bg-platnium" alt="loading gif"/>)}
        </Link>
     
    )


  
}
export default PkmnCard

App

import React from 'react'
import { Route, BrowserRouter, Routes} from 'react-router-dom'
import Navbar from './components/Navbar'
import PkmnList from './components/PkmnList'
import PkmnStats from './components/PkmnStats'



function App (){

    return (  
      <BrowserRouter>
          <div className="w-full">
            <Navbar/>
          
              <Routes>
                <Route path="/" element={<PkmnList/>} />
                <Route path="/pokemon/:name/:index"  element={<PkmnStats/>} />
              </Routes>
            
          </div>
      </BrowserRouter>
    )


  }
export default App;

Any help in figuring this out and cementing this to memory is very welcome and appreciated!


Solution

  • Your pkmnStat is an array of stats objects, not a single object, it doesn't have the stats property. If I follow the logic of your app correctly, you can just ditch the square brackets in useState here:

    const [pkmnStat, setPkmnStat] = useState([{ /* object definition here */}])
    

    and instead do

    const [pkmnStat, setPkmnStat] = useState({ /* object definition here */})