首页 > 解决方案 > while trying to fetch data,i get "Unhandled Rejection (TypeError): undefined is not iterable"

问题描述

I am noob at this, so what i am trying to do is fetch the data of pokeapi.co (pokemon api), so i get an an array with objects,each object has an url, this url you can fetch it and get all the info about the current pokemon...It works fine until i try to map the array with all the object inside and shows the following error...(i tried to use async/await buti not sure how to use it...Help!)

import Card from "./card"
import React,{useState,useEffect} from "react"

function App() {

const [pokemon,setPokemon]=useState()
const[loading,setLoading]=useState(true)

useEffect(
 async ()=>{
    
        return await fetch("https://pokeapi.co/api/v2/ability/?limit=20&offset=20")
      .then(res=> res.json())
      .then( async data=>{return await data.results.map(async element=>{return await fetch(element.url).then(res=>res.json()).then(data=>{return setPokemon(prevState=>{return [...prevState,{data}]})})})})}
    
,[])
 
  return (
    <div className="App">
      <header id="header" className="py-10 w-full bg-yellow-500">
        <h1 className="text-4xl text-center font-bold text-red-700">Pokedex.</h1>
      </header>

     {loading &&  <div class="rounded-t-lg overflow-hidden border-t border-l border-r  text-center p-4 py-12">
        <span class="inline-flex rounded-md shadow-sm">
          <button type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150 cursor-not-allowed" disabled="">
            <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            Processing
          </button>
        </span>
        </div>}
    
      <div className="grid grid-cols-4 gap-4 my-10  px-10">
         {pokemon? pokemon.results.map((element,index)=>{return <Card key={index} name={ element.name.toUpperCase()} />}):null} 
      </div>
        <footer className="text-center text-gray-500 font-bold">2020</footer>
    </div>
  );
}
export default App;

when i fetch this(https://pokeapi.co/api/v2/ability/?limit=20&offset=20) i get thisenter image description here So i try to fetch each url inside the object to get all the info about the current pokemon, so the response is this enter image description here and what i want to do is save this data in state to render it later

im lost with the usage of async/await... If you have a better idea how to implement this, would be great!!

[1]: https://i.stack.imgur.com/6bTR9.png

标签: javascriptreactjsfetch-api

解决方案


Issues

  1. Effect hook callback are synchronously processed, they can't be async
  2. pokemon initial state is undefined, so it isn't iterable
  3. setPokemon treats pokemon state like it is an array, but pokemon state is later accessed as if it were an object, i.e. pokemon.results
  4. The loading state is never cleared
  5. All the pokemon data is nested in a data key, but accessed as if the data were all at the root level, i.e. element.name.toUpperCase()

minor issues

  1. Use className versus class in JSX
  2. Use camelCased attributes, i.e. strokeWidth versus stroke-width

Solution

Provide initial state, I suggest an array. Now the previous state is iterable

const [pokemon, setPokemon] = useState([]);

Render the array directly. Try to avoid using an array index as the react key.

{pokemon.map((element) => <Card key={element.id} name={element.name.toUpperCase()} />)}

Stick to promise chain, or async/await.

useEffect using promise chain. Add a catch block for handling errors, and a finally block to clear the loading state.

useEffect(() => {
  fetch("https://pokeapi.co/api/v2/ability/?limit=20&offset=20")
    // Return response JSON object promise
    .then((res) => res.json())

    // Map array of fetch request promises
    .then((data) => Promise.all(data.results.map((el) => fetch(el.url))))

    // Map array of response JSON object promises
    .then((results) => Promise.all(results.map((res) => res.json())))

    // Update state, copy old state and append new
    .then((data) => setPokemon((pokemon) => [...pokemon, ...data]))

    .catch((error) =>
      console.error("There has been a problem with your fetch operation:", error)
    )
    .finally(() => setLoading(false));
}, []);

Edit while-trying-to-fetch-data-i-get-unhandled-rejection-typeerror-undefined-is (Promise Chain)

useEffect using async/await. Wrap all the fetching logic within an async function and invoke that in the effect hook callback. Add a catch block for handling errors, and a finally block to clear the loading state.

useEffect(() => {
  const pokeFetch = async () => {
    try {
      const res = await fetch("https://pokeapi.co/api/v2/ability/?limit=20&offset=20");

      // Await response JSON object promise
      const data = await res.json();

      // Await map array of fetch request promises
      const urlRes = await Promise.all(data.results.map((el) => fetch(el.url)));

      // Await map array of response JSON object promise
      const results = await Promise.all(urlRes.map((res) => res.json()));

      // Update state, copy old state and append new
      setPokemon((pokemon) => [...pokemon, ...results]);
    } catch (error) {
      console.error("There has been a problem with your fetch operation:", error);
    } finally {
      setLoading(false);
    }
  };

  pokeFetch();
}, []);

Edit while-trying-to-fetch-data-i-get-unhandled-rejection-typeerror-undefined-is (Async/Await)

JSX render. Use className and strokeWidth attributes. Map pokemon unconditionally, the array.prototype.map will handle the empty array state without issue.

return (
  <div className="App">
    <header id="header" className="py-10 w-full bg-yellow-500">
      <h1 className="text-4xl text-center font-bold text-red-700">
        Pokedex.
      </h1>
    </header>

    {loading && (
      <div className="rounded-t-lg overflow-hidden border-t border-l border-r  text-center p-4 py-12">
        <span className="inline-flex rounded-md shadow-sm">
          <button
            type="button"
            className="inline-flex items-center px-4 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150 cursor-not-allowed"
            disabled=""
          >
            <svg
              className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
            >
              <circle
                className="opacity-25"
                cx="12"
                cy="12"
                r="10"
                stroke="currentColor"
                strokeWidth="4"
              ></circle>
              <path
                className="opacity-75"
                fill="currentColor"
                d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
              ></path>
            </svg>
            Processing
          </button>
        </span>
      </div>
    )}

    <div className="grid grid-cols-4 gap-4 my-10  px-10">
      {pokemon.map((element) => (
        <Card key={element.id} name={element.name.toUpperCase()} />
      ))}
    </div>
    <footer className="text-center text-gray-500 font-bold">2020</footer>
  </div>
);

推荐阅读