useEffect not being called and not updating state when api is fetched
Asked Answered
R

6

18

I'm fetching data from a weather api using useEffect hook and declaring the dependency correctly as well. My state is still not being updated and I get errors in my render function because of that. I've pretty much tried everything from getting rid of the dependency array to declaring multiples in the dependency array. I don't know what's wrong with my function. The API's JSON response is in this format:

{
 location: {
 name: "Paris",
 region: "Ile-de-France",
 },
 current: {
  last_updated_epoch: 1564279222,
  last_updated: "2019-07-28 04:00",
  temp_c: 16,
  temp_f: 60.8,
  is_day: 0,
  condition: {
    text: "Clear",
    icon: "//cdn.apixu.com/weather/64x64/night/113.png",
    code: 1000
  },
  wind_mph: 6.9,
  wind_kph: 11.2
 }
}

and this is what my code looks like:

const Weather = ({ capital }) => {
  const [weather, setWeather] = useState(null);

  useEffect(() => {
    console.log("useEffect called");
    const getWeather = async () => {
      try {
        const res = await axios.get(
          `http://api.apixu.com/v1/current.json?key=53d601eb03d1412c9c004840192807&q=${capital}`
        );
        setWeather(res.data);
      } catch (e) {
        console.log(e);
      }
    };
    getWeather();
  }, [capital]);
  console.log(weather);

  return (
    <Card style={{ width: "18rem", marginTop: "25px" }}>
      <Card.Img variant="top" src={weather.current.condition.icon} />

      <Card.Header style={{ textAlign: "center", fontSize: "25px" }}>
        Weather in {capital}
      </Card.Header>
    </Card>
  )
}

I expect to get to be shown image of the icon but I get this error message in my render function:

TypeError: Cannot read property 'current' of null
Weather
src/components/Weather.js:26
  23 | 
  24 | return (
  25 |   <Card style={{ width: "18rem", marginTop: "25px" }}>
  26 |     <Card.Img variant="top" src={weather.current.condition.icon} />
     | ^  27 | 
  28 |     <Card.Header style={{ textAlign: "center", fontSize: "25px" }}>
  29 |       Weather in {capital}

and my console.log(weather) return null, the original state even though its being called after useEffect() and console.log(useEffect called) does not log at all which mean useEffect is not being called.

Ringnecked answered 30/7, 2019 at 2:22 Comment(2)
Remove your dependency [capital]Unsaid
I've tried removing the dependency array altogether, but it still doesn't solve the issue and i've also tried with just [] but doesn't work. However, I do need to re-render and update state if capital changes so I know for sure [] this isn't the answer.Ringnecked
G
26

The error message gives it away, Cannot read property 'current' of null, the only place where current is called is in weather.current in the src of Card.Img, so we deduce that weather was null during the render.

The reason this happens is because the api call is asynchronus, it doesn't populate the state immediately, so the render happens first and tries to read .current from the initial weather state null.

Solution: in your render method, make sure not to read weather.current while weather is null.

You can for example use {weather && <Card>...</Card} to hide the whole card until the data is loaded and show a loading indicator, or you can use src={weather && weather.current.condition.icon} as a quick workaround.

const Weather = ({capital}) => {
  const [weather, setWeather] = useState(null);

  useEffect(() => {
    console.log("useEffect called");
    const getWeather = async () => {
      try {
        const res = await axios.get(
          `http://api.apixu.com/v1/current.json?key=53d601eb03d1412c9c004840192807&q=${capital}`,
        );
        setWeather(res.data);
      } catch (e) {
        console.log(e);
      }
    };
    getWeather();
  }, [capital]);
  console.log(weather);

  return (
    <Card style={{width: "18rem", marginTop: "25px"}}>
      <Card.Img variant="top" src={weather && weather.current.condition.icon} />

      <Card.Header style={{textAlign: "center", fontSize: "25px"}}>
        Weather in {capital}
      </Card.Header>
    </Card>
  );
};
Glyoxaline answered 30/7, 2019 at 2:28 Comment(2)
WOW that worked. Even the console.log(weather) is populating and useEffect called is also being logged to console. I understand why I had to check for weather in my render function but how come my console.log(weather) was not logging the weather data or my state was not being updated with my setWeather()?Ringnecked
the reason this happens is because the api call is asynchronus, it doesn't populate the state immediately, so the render happens first and tries to read .current from the initial state nullGlyoxaline
S
3

I had the same puzzling issue one time

You can try adding a key prop on the component when it is created in the parent code

<yourcomponent key="some_unique_value" />

This is because in most cases, when your component is reused, based on the way it is created, it may usually re-render it with some changes instead of creating it again when you reuse it, Hence the useEffect is not going to be called. eg in SwitchRoute, loops, conditionals...

So adding a key will prevent this from happening. If it is in a loop, you need to make sure each element is unique, maybe by including the index i in the key if you can't find any better unique key.

Steadfast answered 7/7, 2021 at 16:23 Comment(0)
T
2

So, I was facing a similar issue, where it seemed like useEffect hook was not getting invoked at all. The concerned code snippet is given below:

Within my functional component, this was the sequence of the appearance of the code:

  • a variable const facetFields = []; declaration which was supposed to be set by an AJAX call from within useEffect hook

  • useEffect hook within which the above variable was getting set with AJAX.

useEffect( ()=>{
    console.log('FacetTreeView: useEffect entered');
    facetFields = getFacetFieldNames(props.tabIndex);   
 }, []);
  • JSX code that uses the variable.
return (
    <MyComponent>
        {facetFields.map((facetLabel, index) => {
            populateFacetInstances(facetLabel) }) 
        }
    </MyComponent>
)

With this code, the console statement inside the hook was not printing at all. Also, I kept getting a undefined error while map'ing the facetFields.

So, it turns out that the useHook is called after the component is rendered. So, during the rendering, the variable facetFields is undefined. So, I fixed this by adding this line to my JSX rendering part of code:

facetFields && facetFields.map(...);

Once, I made the above change, the control started going to the hook. So, in summary, if there is any code within JSX that is being set from useEffect hook, then it's best to defer the execution of the JSX till hook execution is completed. Although, this is not mandatory, but it will make the JSX code easier to read. This can be achieved in a simple way by using a boolean state flag.

  1. Define the state:
const [readyForRender, setReadyForRender] = React.useState(false);
  1. Set state in hook.
useEffect(()=> {
    console.log('FacetTreeView: useEffect entered');
    facetFields = getFacetFieldNames(props.tabIndex);
    setReadyForRender(true); 
}, []);
  1. Render JSX conditionally.
if(readyForRender){
    return (
        <MyComponent>
            {facetFields.map((facetLabel, index) => {
                populateFacetInstances(facetLabel)
            })}
        </MyComponent>
    );
} else{
    return null;
}
Tiffa answered 1/10, 2020 at 18:2 Comment(0)
N
0

I am having similar issue. It works in sometimes and most of the times it doesn't. I have tried updating the code based on solution suggested above but no luck. I am trying here to pass the data received from EDIdata.json to child component . But at first place, the data is not being fetched from the API as getData() is not invoked on page load.

import { React, useEffect, useState } from 'react';
import MilestoneSummary from './MilestoneSummary';
import { Grid, Column } from '@carbon/react';

const OrderSummary = () => {
    const [data, setData] = useState({ data: {} });
    useEffect(() => {
        getData()
    })

    const getData = () => {
        fetch('EDIData.json'
            , {
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                }
            }
        )
            .then(function (response) {
                console.log("JSON response" + response)
                return response.json();
            })
            .then(function (myJson) {
                setData(myJson)
            })
            .catch(err => { console.log(err) })
    }

    console.log(data);
    return (
        <>
            <Grid>
                <Column md={4} lg={4} sm={4}>
                    <h3 className="landing-page__label">Order milestone summary</h3>
                    <MilestoneSummary summaryItems={data.OrderMilestoneSummary} />
                </Column>
            </Grid>
        </>
    )
};

export default OrderSummary;

The console.log for data just logs nothing. Kindly advise.

Nariko answered 9/4, 2023 at 3:27 Comment(0)
U
0

Since the UseEffect runs an async fetch and renders happens behind the scene, React tries to render the HTML, but the data has not been fetched yet. So, weather is null. Put a condition in the JSX and make sure that weather is not null if you want to display

    weather.current.condition.icon
Unbonnet answered 23/4, 2023 at 5:29 Comment(0)
I
0

I'm commenting here cause i was not able to find this anywhere. I was having the same issue (useEffect was not executing at all) and turned out it was because my functional component was mistakenly defined as async (might have been prettier or something).

Removing the async from the component definition fixed it.

Before:

const SomeComponent = async () => { //<--- this is the issue
    useEffect(() => {console.log("TEST")},[]); // This will never fire
    return <></>
}

After:

const SomeComponent = () => { // Removed async from definition
    useEffect(() => {console.log("TEST")},[]); // Working normally.
    return <></>
}

Hope its useful.

Inkblot answered 7/6, 2023 at 9:32 Comment(2)
what if you have a use cases to make an API call from functional component, and has to be async ?Spangler
@Spangler The component itself doesn't need to be async, you can define an async method inside of the component. Also if you need to use an async call inside of a useEffect, the useEffect doesn't need to be marked as async but you should define an async function inside of it. Hope this helps you.Lemuelah

© 2022 - 2024 — McMap. All rights reserved.