skip to Main Content

I have a useFetchPokemon hook that includes the following code:

import { useState, useEffect } from "react";

export const useFetchPokemon = () => {
  const initialUrl = "https://pokeapi.co/api/v2/pokemon"
  const [pokemon, setPokemon] = useState([]) as any;
  const [nextUrl, setNextUrl] = useState(null)
  const [previousUrl, setPreviousUrl] = useState(null)

  const getPokemonDetails = async (listOfPokemon: any) => {
    const pokemonWithDetails = await Promise.all(
      listOfPokemon.map(async (pokemon: any) => {
        const response = await fetch(pokemon.url);
        const data = await response.json();
        return data;
      })
    );

    return pokemonWithDetails;
  };

  useEffect(() => {
    const getInitalData = async () => {
      const response = await fetch(initialUrl);
      const { results, next, previous } = await response.json();
      const pokemon = (await getPokemonDetails(results)) as any;

      setNextUrl(next);
      setPreviousUrl(previous);
      setPokemon(pokemon);
    };

    getInitalData();
  }, []);

  const getNextPokemon = async () => {
    if(!nextUrl) return
    const response = (await fetch(nextUrl)) as any
    const { results, next, previous } = await response.json();

    const newPokemon = await getPokemonDetails(results)

    setNextUrl(next)
    setPreviousUrl(previous)
    setPokemon(newPokemon)
  }

  const getPreviousPokemon = async () => {
    if(!previousUrl) return
    const response = (await fetch(previousUrl)) as any
    const { results, next, previous } = await response.json();

    const previousPokemon = await getPokemonDetails(results)

    setNextUrl(next)
    setPreviousUrl(previous)
    setPokemon(previousPokemon)
  }

  return { pokemon, nextUrl, previousUrl, getNextPokemon, getPreviousPokemon }
}

I then have a PokemonList component that uses the hook:

import type { FC } from "react";
import { useFetchPokemon } from "../hooks/useFetchPokemon";

const PokemonList: FC = () => {
  const { pokemon } = useFetchPokemon()

  return (
    <div>
      Pokemon List
      <ul>
        {pokemon.map((p: any) => (
          <li key={p.name}>
            <img
              alt={p.name}
              style={{ width: "150px", height: "150px", objectFit: "cover" }}
              src={p.sprites.front_default}
            />
            <p>{p.name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default PokemonList;

In my App component I have to buttons to fetch the next set of Pokemon and the Previous set:

import PokemonList from './components/PokemonList';
import { useFetchPokemon } from './hooks/useFetchPokemon';

function App() {
  const { previousUrl, nextUrl, getNextPokemon, getPreviousPokemon } = useFetchPokemon();
  return (
    <div className="App">
      {previousUrl && <button onClick={() => getPreviousPokemon()}>Previous</button>}
      {nextUrl && <button onClick={() => getNextPokemon()}>Next</button>}
      <PokemonList />
    </div>
  );
}

export default App;

When I click on the next button, the getNextPokemon call runs correctly and returns the correct data, but setting the new pokemon data to the state doesn’t update the PokemonList component. I can’t figure it out for the life of me…

2

Answers


  1. The issue here is that you are using the useFetchPokemon hook in both the <App /> and <PokemonList /> components. This creates two separate instances of the hook, each with its own state. When you update the state in one instance of the hook, it doesn’t affect the other instance.

    You could try to use your useFetchPokemon hook inside <App /> only and pass the required data to the <PokemonList /> component through props.

    Plus:

    Also, if you would like to achieve "shared" experience, you can use React Context:

    import { createContext, useContext } from "react";
    // fetchPokemon here is your old one "useFetchPokemon" 
    import { fetchPokemon } from "../fetchPokemon";
    
    const PokemonContext = createContext(null);
    
    export const useFetchPokemon = () => useContext(PokemonContext);
    
    export const PokemonProvider = ({ children }) => {
      const pokemonData = fetchPokemon();
      return (
        <PokemonContext.Provider value={pokemonData}>
          {children}
        </PokemonContext.Provider>
      );
    };
    

    You just need to wrap up your components like that:

    const App = () => {
      return (
        <PokemonProvider>
          <div className="App">
            <Navigation />
            <PokemonList />
          </div>
        </PokemonProvider>
      );
    }
    

    So you can access shared data inside the components:

    import { useFetchPokemon } from "../context/PokemonContext";
    ...
    const { pokemon } = useFetchPokemon();
    
    Login or Signup to reply.
  2.  const pokemon = (await getPokemonDetails(results)) as any;
    
      setNextUrl(next);
      setPreviousUrl(previous);
      setPokemon(pokemon);
    

    don’t use pokemon name, first copy data to a new variable and then set it

    const pokemon = (await getPokemonDetails(results)) as any;
    const tempPokemon = pokemon
    setPokemon(tempPokemon);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search