Reputation: 65
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>
)
}
Upvotes: 1
Views: 90
Reputation: 12222
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, i.e. 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>
)
}
Upvotes: 2
Reputation: 1323
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>
)
Upvotes: 0
Reputation: 788
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
Upvotes: 0