简体   繁体   中英

Make an API call on second render using React Hooks

What I'm trying to do is get data from a weather API but what happens is on the first render the 'data' state is empty and on second render the API is called and the data is set. This makes it so when I try to access the data later for an image I get an error, TypeError: Cannot read property 'weather' of undefined , because it is initially empty. Somehow I need to keep track of how many times a render happens or change how I fetch the data. I thought useEffect with an empty list as the second argument would act as componentDidMount . Here is my code:

import React, {useState, useEffect} from 'react';

export default function App() {
  const useFetch = () =>{
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect( () => {
      async function fetchData(){
      const response = await fetch('https://api.weatherbit.io/v2.0/forecast/hourly?city=Chicago,IL&key=XXX&hours=24');
      const json = await response.json();
      setData(json.data);
      setLoading(false);
      }
      fetchData();
    }, []);
    return {data, loading};
  }

  const convert = (c) =>{
    let f = (c*(9/5))+32;
    return f
   }


  const {data, loading} = useFetch();
  console.log(data)
  return (
    <div>
      <img src={'https://www.weatherbit.io/static/img/icons/'+data[0].weather.icon+'.png'}/>
      <h1>{loading ? 'Loading...' : ''}</h1>
    </div>
  )
}


The function passed to useEffect will run after the render is committed to the screen and by default, effects run after every completed render. You need to render the img only when api call is done, ie when loading is false .

import React, {useState, useEffect} from 'react';

export default function App() {
  const useFetch = () =>{
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect( () => {
      async function fetchData(){
      const response = await fetch('https://api.weatherbit.io/v2.0/forecast/hourly?city=Chicago,IL&key=XXX&hours=24');
      const json = await response.json();
      setData(json.data);
      setLoading(false);
      }
      fetchData();
    }, []);
    return {data, loading};
  }

  const convert = (c) =>{
    let f = (c*(9/5))+32;
    return f
   }


  const {data, loading} = useFetch();

  if(loading) {
     return <h1>{'Loading...'}</h1>
  }

  return (
    <div>
      <img src = {'https://www.weatherbit.io/static/img/icons/'+data[0].weather.icon+'.png'}/>
    </div>
  )
}

You can try something like this

const [data, setData] = useState(null);

return (
   {data ? ( <div>
     <img src={'https://www.weatherbit.io/static/img/icons/'+data[0].weather.icon+'.png'}/>
     <h1>{loading ? 'Loading...' : ''}</h1>
   </div>) : <p>Loading....<p>}
 )

This sets data to null at first render and the conditional rendering in the render function checks if data is truth/ or has data and if not returns the loading... text

You could put a conditional in the render function

  return (
    <div>
      {data && data.weather && <img src={'https://www.weatherbit.io/static/img/icons/'+data[0].weather.icon+'.png'}/>}
      <h1>{loading ? 'Loading...' : ''}</h1>
    </div>
  )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM