Using UseEffect Hook to Fetch API

Viewed 2546

I am trying to fetch cryptocurrency data from an api, by updating my state with the information pulled up. But when I do, my console.log is showing an empty object. And there is no data being passed down to my child components either. Did I do something wrong here?

import React, {useState, useEffect} from 'react';
import SmallBox from './SmallBox';
import './App.css';

function App() {
  const [smallData, setSmallData] = useState({})

  async function getAPI(){
    await fetch(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false
    `)
    .then(response => response.json())
    .then(data => setSmallData({data})); 
  }

  useEffect( () => {
    getAPI();
    console.log(smallData)
  }, []);


  return (
    <div className="app">
      
      {/* SmallBox - balance */}
      <SmallBox className="balance"/>
      
      {/* SmallBox - bitcoin */}
      <SmallBox className="bitcoin" coin={smallData}/>
      
      {/* SmallBox - ethereum */}
      <SmallBox className="ethereum" coin={smallData}/>
      
      {/* SmallBox - ripple */}
      <SmallBox className="ripple" coin={smallData}/>
      
      {/* SmallBox - tether */}
      <SmallBox className="tether" coin={smallData}/>
     
    </div>
  );
}

export default App;
5 Answers

Try awaiting for the data to be fetched before logging it, instead of await for the fetch inside the async function getAPI:

  function getAPI(){
    return fetch(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false
    `)
      .then(response => response.json())
      .then(data => setSmallData({data})); 
  }

  useEffect( () => {
    await getAPI();
    console.log(smallData)
  }, []); // <- Add dependencies here

Note: I think getAPI is going to be called on every render of the component, which I don't think it's what you want. I suggest to add some dependency to the second variable (an empty array) passed to the useEffect function, telling React to call this effect only when that/those variables have changed. For example, you can add the currency.

Try this:

async function getAPI(){
    const rawData = await fetch(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`)
    const jsonData = rawData.json();
    setSmallData( state => ({...jsonData}) );
  }

useEffect(() => {
    getAPI().then(() => console.log(smallData));
  }, []);

change useEffect wait for the getApi function to finish.

  useEffect(() => {
    getAPI().then((_) => {
      console.log(smallData);
    });
  }, []);

First, your console.log should display {} because that's the initial state of smallData, and getAPI will be executed asynchronously. There's nothing wrong about that.

But with your problem of no data being passed down to children components, it might be because you are setting the smallData to { data: *api response* }, maybe you want smallData to be the api response, instead of an object with a data property.

I would suggest to put console.log(smallData) just before the return statement, just to see its content in every render.

If you still are facing problems, add the SmallBox component code, maybe the problem is there

Your React set State hooks are called asynchronously. Thus, your smallData will show the previous state because the component has not rerendered yet.

You didn't do anything wrong. See below for commented out code to see what's going on.

  useEffect( () => {
    getAPI(); // 1. This call seems to be correct, and queues up setSmallData. 
    // 2. The state update has not happened yet, but the setSmallData call is queued up. 
    console.log(smallData) // 3. still {} because the state has not been updated yet, and thus is still showing the current state. 

  }, []);

In other words, the console.log(smallData) runs BEFORE your setSmallData, and that's why you see an empty object(because {} is your initial state). If you displayed the data somewhere on the page however, you will notice that the component rerenders with the data.

You can try:

    async function getAPI(){
      return await fetch(`...`)
      .then(response => response.json())
      .then(data => data); 
    }
    useEffect( () => {
     async function getApiAndSetState() {
     let data = await getAPI(); // the code pauses here until getAPI() completes.
     console.log(data); // this data is now available
     setSmallData(data);
     }
   }, []);
Related