Search code examples
javascriptreactjstypescriptpokeapi

How to Call API in a Loop in React


I have a list of Pokemon names that I am looping through and using to call the PokeApi. Then, I want to take the returned data and output a simple display.

I am encountering issues saving the data and returning it, but I don't know where I'm going wrong. My output never has all of the pokemon that I would expect it to have and the return values are inconsistent with previous ones. So there might be issues with data being overwritten or synchronicity, but none of my attempts to fix it have worked.

Here's the code:

App.tsx

import { gen1List } from 'data';
import React from 'react';
import { Pokemon } from 'models';
import { useFetchPokemon } from 'api/useFetchPokemon';

export const App = () => {
    const gen1Pokemon = useFetchPokemon(gen1List);

    return (
        <>
            {gen1Pokemon.map((pokemon: Pokemon, index: number) => (
                <div key={index}>
                    {pokemon.name}
                    <img src={pokemon.sprite} alt={pokemon.name} />
                </div>
            ))}
        </>
    );
};

useFetchPokemon.ts

import { Pokemon, filterPokemonData } from "models";
import { useEffect, useState } from "react"
import { fetchPokemon } from "api/fetchPokemon";

export const useFetchPokemon = (pokemonList: string[]) => {
    const [pokemon, setPokemon] = useState<Pokemon[]>([]);
    const pokemonArray: Pokemon[] = [];

    useEffect(() => {
        for (const mon of pokemonList) {
            fetchPokemon(mon).then((result) => {
                pokemonArray.push(filterPokemonData(result));
            });
        };

        setPokemon(pokemonArray);
    }, []);

    return pokemon;
};

fetchPokemon.ts

export const fetchPokemon = async (name: string): Promise<any> => {
    const response = await fetch('https://pokeapi.co/api/v2/pokemon/' + name);
    return await response.json();
};

models.ts

export interface Pokemon {
    name: string;
    sprite: string;
};

export const filterPokemonData = (pokemon): Pokemon => {
    return {
        name: pokemon.name,
        sprite: pokemon.sprites.front_default,
    }
};

data.ts

export const gen1List: string[] = [
    'bulbasaur',
    'venusaur',
    'charizard',
];

I've tried making the call a lot of different ways, with or without a hook. I've experimented with using useCallback, putting the loop in its own async function and calling that, setting the state inside the loop, etc.


Solution

  • To make the code more efficient and easy to understand, you can use Promise.all to fetch data for all the Pokémon simultaneously and then update the state when all the promises have resolved. Here's an updated version of your custom hook:

    import { useEffect, useState } from "react";
    import { fetchPokemon } from "api/fetchPokemon";
    
    export const useFetchPokemon = (pokemonList: string[]) => {
        const [pokemon, setPokemon] = useState<Array<Pokemon>>([]);
    
        useEffect(() => {
            const fetchData = async () => {
                try {
                    const promises = pokemonList.map((mon) => fetchPokemon(mon));
                    const pokemonData = await Promise.all(promises);
                    const filteredPokemon = pokemonData.map(filterPokemonData);
                    setPokemon(filteredPokemon);
                } catch (error) {
                    console.error("Error fetching Pokémon:", error);
                }
            };
    
            fetchData();
        }, [pokemonList]);
    
        return pokemon;
    };
    

    In this updated code:

    1. We use Promise.all to fetch Pokémon data for all the items in the pokemonList array concurrently, which should be faster than making sequential requests.

    2. We handle any errors that might occur during the data fetching process and log them to the console.

    3. The custom hook now takes the pokemonList array as a dependency for the useEffect, ensuring that it will re-run whenever pokemonList changes.

    4. We simplify the code by directly setting the filteredPokemon array in the setPokemon function. This avoids the need for an intermediate pokemonArray variable.