Search code examples
reactjsapiuse-effect

How to pass API data into state in React


I have been learning react for the past week and i am finding it really interesting. However, i am stuck. I am making a call to an API and using a custom fetch hook to get the data. This is represented where i have declaring a constant and extracting the data along with other stuff which are general functions to show error and show a loader before the data is displayed. Then i pass this data as a prop into another component called animelist which is responsible for mapping through the data. What i want to do now is to pass this data into a state so i can modify it later as required. I then want to pass the state as a prop to the animelist component rather than the data directly. I have declared a piece of state called topAnime. Then i am using the useEffect hook to set the topAnime state to the data. However i get an error in the console saying that data is basically null which i don't understand how. I feel like it might be something to do with the dependency array in the useEffect hook but i am not sure. Any guidance would be much appreciated. Below is my code. Thanks.

import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {

  const [topAnime, setTopAnime] = useState([])
  const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
  useEffect(() => {
    (async () => {
      setTopAnime(data);
    })();
  }, [data]);

  return (
    <div className="top-anime">
    <Header/>
    {error && <Error/>}
    {isPending && <Spinner/>}
    {data && <h1>Top Anime</h1>}
    {data && <AnimeList animelist={topAnime}/>}
  </div>
  )
}

export default TopAnime

Here is the error i get enter image description here

Update: I managed to fix it myself somehow lol. All i had to do was add && topAnime like i have done for data. I am not sure why that fixed it but an explanation would be really helpful. Below is the working code now.

import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {

  const [topAnime, setTopAnime] = useState([])
  const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
  useEffect(() => {
    (async () => {
      setTopAnime(data);
    })();
  });
  return (
    <div className="top-anime">
    <Header/>
    {error && <Error/>}
    {isPending && <Spinner/>}
    {data && <h1>Top Anime</h1>}
    {data && topAnime && <AnimeList animelist={topAnime}/>}
  </div>
  )
}

export default TopAnime

Solution

  • Editted answer:

    I see that you found your answer. The reason why it works, if I recall correctly, is because when you initially load the apiData, topAnime is still null as the calls are made asynchronously. Basically it tries to render your AnimeList with the (empty) topAnime prop before the Data is loaded, thus giving you the error that it's null. A better way to fix it (if you'd like more components to use the topAnime data) is to check for it at the start of the return, like this:

     return ( topAnime ? (
        <div className="top-anime">
        <Header/>
        {error && <Error/>}
        {isPending && <Spinner/>}
        {data && <h1>Top Anime</h1>}
        {data && <AnimeList animelist={topAnime}/>}
      </div> ) : (<h1>loading..</h1>)
      )
    

    This will first check if topAnime contains data and if not it will show loading until it does.

    Old answer:

    If you move the api call inside the useEffect you should be good. I used axios instead of useFetch for this example and I removed some clutter to make it more obvious to see what's happening. Here's the codesandbox link where you can see the data being loaded in the console: https://codesandbox.io/s/examplegetdata-bk56bi

    import { useState, useEffect } from "react";
    import axios from "axios";
    function TopAnime() {
      const [topAnime, setTopAnime] = useState([]);
    
      useEffect(() => {
        (async () => {
          const data = await axios.get("https://api.jikan.moe/v4/top/anime");
          setTopAnime(data);
        })();
      }, []);
    
      return <div className="top-anime">{console.log(topAnime)}</div>;
    }
    
    export default TopAnime;