Home > other >  Promise only resolves correctly on page refresh
Promise only resolves correctly on page refresh

Time:03-24

I am playing around with an API that gets a list of Pokemon and corresponding data that looks like this.

export function SomePage() {
const [arr, setArray] = useState([]);

   useEffect(() => {
    fetchSomePokemon();
  }, []);


  function fetchSomePokemon() {
    fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
     .then(response => response.json())
     .then((pokemonList) => {
       const someArray = [];
     pokemonList.results.map(async (pokemon: { url: string; }) => {
       someArray.push(await fetchData(pokemon))
     })
     setArray([...arr, someArray]);
    })
   }

   async function fetchData(pokemon: { url: string; }) {
    let url = pokemon.url
     return await fetch(url).then(async res => await res.json())
    }

    console.log(arr);

  return (
      <div>
      {arr[0]?.map((pokemon, index) => (
          <div
            key={index}
          >
            {pokemon.name}
          </div>
        ))
      }
      </div>
  );
}

The code works(kind of) however on the first render the map will display nothing even though the console.log outputs data. Only once the page has been refreshed will the correct data display. I have a feeling it's something to do with not handling promises correctly. Perhaps someone could help me out. TIA

Expected output: Data populated on initial render(in this case, pokemon names will display)

CodePudding user response:

The in-build map method on arrays in synchronous in nature. In fetchSomePokemon you need to return a promise from map callback function since you're writing async code in it.

Now items in array returned by pokemonList.results.map are promises. You need to use Promise.all on pokemonList.results.map and await it.

await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
       return fetchData.then(someArray.push(pokemon))
     }));

CodePudding user response:

On your first render, you don't have the data yet, so arr[0] doens't exist for you to .map on it, so it crashes. You need to check if the data is already there before mapping.

Using optional chaining, if there's no data it will not throw an error on your first render and it will render correctly when the data arrive and it re-renders.

...
  return (
    <div>
      {arr[0]?.map((pokemon, index) => (
        <div key={index}>{pokemon.name}</div>
      ))}
    </div>
  );
}

CodePudding user response:

in

useEffect(() => { fetchSomePokemon(); }, []);

[] tells react there is no dependencies for this effect to happen,

read more here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

CodePudding user response:

One way to solve your issues is to await the data fetching in useEffect().

Here's a POC:

export function Page() {
  const [pokemon, setPokemon] = useState([]);

  // will fetch the pokemon on the first render
  useEffect(() => {
    async function fetchPokemon() {
      // ... logic that fetches the pokemon
    }
    
    fetchPokemon();
  }, []);

  if (!pokemon.length) {
    // you can return a spinner here
    return null;
  }
  
  return (
    <div>
      {pokemon.map(item => {
        // return an element
      })}
    </div>
  );
 }
  • Related