I’m currently working on a project with the Pokemon API and i’m facing a problem. I want to change the value parameter in the async function getPokemonTypes(), but the value I receive in handleSelect() is not working. On the console.log(value), the value changes every time I select a different option. Could someone tell me what I’m doing wrong?
import React from 'react' import { useState, useEffect } from "react"; import { Link } from 'react-router-dom' async function getPokemonTypes(value) { const response = await fetch(`https://pokeapi.co/api/v2/type/${value}`) const data = await response.json() console.log(data) return data } async function getPokemonInfo(pokemonId) { const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonId}`) const data = await response.json() return data } export const PokemonTypesCard = () => { const [pokemonTypesCard, setPokemonTypesCard] = useState({}) const [pokemonIdNumber, setPokemonIdNumber] = useState([]) const [value, setValue] = useState('normal') const handleSelect = (value) => { setValue(value) } console.log(value) useEffect(() => { async function fetchData() { const pokemonTypesCard = await getPokemonTypes(value) const pokemonIdText = pokemonTypesCard.pokemon.map((item) => { return item.pokemon.name }) const data = pokemonIdText.map(async (pokemonId) => { return ( await getPokemonInfo(pokemonId) ) }) const pokemonIdNumber = await Promise.all(data) setPokemonIdNumber(pokemonIdNumber) setPokemonTypesCard(pokemonTypesCard) } fetchData() }, []) return ( <section> <div> <label htmlFor='pokemon-types'>Choose a pokemon type</label> <form> <select onChange={(event) => handleSelect(event.target.value)} value={value}> <option value='normal'>Normal</option> <option value='fighting'>Fighting</option> <option value='flying'>Flying</option> <option value='poison'>Poison</option> <option value='ground'>Ground</option> <option value='rock'>Rock</option> <option value='bug'>Bug</option> <option value='ghost'>Ghost</option> <option value='steel'>Steel</option> <option value='fire'>Fire</option> <option value='water'>Water</option> <option value='grass'>Grass</option> <option value='electric'>Electric</option> <option value='psychic'>Psychic</option> <option value='ice'>Ice</option> <option value='dragon'>Dragon</option> <option value='dark'>Dark</option> <option value='fairy'>Fairy</option> <option value='shadow'>Shadow</option> </select> </form> </div> {<div> <ul> {!pokemonIdNumber ? '' : pokemonIdNumber.map((item, index) => <li key={index}> <Link to={`/pokemon/${item.id}`}> <img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${item.id}.png`} alt={item.name} /> </Link> <p>{item.name}</p> </li> )} </ul> </div>} </section> ); }
Advertisement
Answer
You need to add type
to the dependnecies array of useEffect
:
useEffect(() => { async function fetchData() { const pokemonTypesCard = await getPokemonTypes(value); const pokemonIdText = pokemonTypesCard.pokemon.map((item); => { return item.pokemon.name; }); const data = pokemonIdText.map(async (pokemonId) => { return ( await getPokemonInfo(pokemonId) ); }); const pokemonIdNumber = await Promise.all(data); setPokemonIdNumber(pokemonIdNumber); setPokemonTypesCard(pokemonTypesCard); } fetchData(); }, [value]); // <= HERE
Keep in mind this code has some issues, as you might end up seeing data for a type that doesn’t match the one in the URL if something like this happens:
- You select
fire
andgetPokemonTypes('fire')
is called. - You select to
ice
andgetPokemonTypes('ice')
is called. getPokemonTypes('ice')
finishes loading and the rest of thefetchData
function executes.getPokemonTypes('fire')
finishes loading and the rest of thefetchData
function executes.- The selected option is now
ice
but see data fromfire
.
The proper way to do it would be like this:
useEffect(() => { let shouldUpdate = true; async function fetchData() { const pokemonTypesCard = await getPokemonTypes(value); if (!shouldUpdate) return; const pokemonIdText = pokemonTypesCard.pokemon.map((item) => { return item.pokemon.name; }); const data = pokemonIdText.map((pokemonId) => { return getPokemonInfo(pokemonId); }); const pokemonIdNumber = await Promise.all(data); if (!shouldUpdate) return; setPokemonIdNumber(pokemonIdNumber); setPokemonTypesCard(pokemonTypesCard); } fetchData(); // This cleanup function will be called if the `useEffect`'s // dependencies change, so if you run it twice in a row for // different types, when the result of the first one arrives, // it will be discarded: return () => { shouldUpdate = false; }; }, [value]);
Also, you have an error here:
const data = pokemonIdText.map(async (pokemonId) => { return ( await getPokemonInfo(pokemonId) ); }); const pokemonIdNumber = await Promise.all(data);
You want to store all those promises in the data
array and await them all together with Promise.all
below, but you are instead awaiting them one by one. It should be like this:
const data = pokemonIdText.map((pokemonId) => { return getPokemonInfo(pokemonId); }); const pokemonIdNumber = await Promise.all(data);