Fernando
Fernando

Reputation: 39

I want to know why my useEffect function is not running on the first render?

So I am creating an app that utilizes the REST countries API and I am trying to call to the API on the first render and from my understanding, in order to do this you have to use an empty array in the useEffect function as such

const LightMode = () => {
    const data = useRef([])

    useEffect(() =>{
        axios.get('https://restcountries.com/v3.1/all').then(res=>{
            res.data.forEach(country =>{
                //console.log(country)
                data.current.push({
                    name: country.name.common,
                    population: country.population,
                    region: country.region,
                    capital: country.capital,
                    image: country.coatOfArms.png
                })
            })
        })
    }, [])

    console.log(data)

    return(
        <div>
            <NavigationBar />
            <div className='temp'>
                <Card className='country-cards'>
                    <Card.Img variant='top' src={data[0].image}/>
                    <Card.Body>
                        <Card.Title></Card.Title>
                    </Card.Body>
                </Card>
            </div>
        </div>
    )
}

but when I run the app I get an error saying unable to read undefined so the first render technically never runs? I want to know why that is. I am still learning how first renders work so any help is much appreciated, also if there is any more information needed let me know.

Error: Error pic

Upvotes: 2

Views: 1686

Answers (5)

LihnNguyen
LihnNguyen

Reputation: 865

At here, I use new_contries to save country when i fillter country(name, population, region, capital, image). After that, i SetContries (new_countries)

const LightMode = () => {
  const [countries, setCountries] = useState([]);

  useEffect(() => {
    axios.get("https://restcountries.com/v3.1/all").then((res) => {
      let new_countries: any = [];
      res.data.forEach((country: any) => {
        new_countries = [...new_countries, {
          name: country.name.common,
          population: country.population,
          region: country.region,
          capital: country.capital,
          image: country.coatOfArms.png,
        }];
      });
      setCountries(new_countries);
    });
  }, []);

  return (
    <div>
      {countries?.map((country) => (
        <>
          <div>{country["name"]}</div>
        </>
      ))}
    </div>
  );
}

Upvotes: 0

Kanti vekariya
Kanti vekariya

Reputation: 697

use state instead of ref with async API call.

import axios from "axios";
import { useEffect, useState } from "react";


const LightMode = () => {
  const [country, setCountry] = useState([]);

  useEffect(() => {
    const getCountries = async () => {
      const countries = await axios.get("https://restcountries.com/v3.1/all");
      const filterCountries = countries.data.map((country) => {
        return {
          name: country.name.common,
          population: country.population,
          region: country.region,
          capital: country.capital,
          image: country.coatOfArms.png
        };
      });
      setCountry(filterCountries);
    };
    getCountries();
  }, []);

  return (
    <div>
      <NavigationBar />
      {country.map((country) => (
        <>
          <div className="temp">
            <Card className="country-cards">
              <Card.Img variant="top" src={country.image} />
              <Card.Body>
                <Card.Title></Card.Title>
              </Card.Body>
            </Card>
          </div>
        </>
      ))}
    </div>
  );
};

export default LightMode;

Upvotes: 0

SNikhill
SNikhill

Reputation: 504

You are combining two things:

  • When useEffect is Run
  • Receiving a Response from the server

To actually test out whether useEffect ran, you can:

  • Add a console.log before that request and see if it got logged
  • Inspect your Network Tab to see if a Request to the Endpoint was triggered

Since your UI is dependent upon the response, it will be a good thing to add a:

  • Loader
  • Fallback component in case the response received is not valid

useEffect is supposed to run on every render unless a dependency array is provided, which will ensure that it only runs if a dependency changes. By providing an empty array, you are limiting useEffect to run on JUST the first render.

Upvotes: 0

raz-ezra
raz-ezra

Reputation: 559

There are two issues at play here:

  1. You are using Ref, which when updates will not trigger a re-render, which means once your REST call returns, the component will not re-render with your new data. Try using state instead of ref.

  2. useEffect does run on the first render, but it is not blocking the render. Meaning, on the first render, useEffect is triggered, but it does not wait for the REST call to return before rendering the component. There are several methods to deal with it:

    • you can return null of there are no items in the array
    • you can use optional chaining (as the answer above suggests)
    • you can display a loading screen if there are no items in the array

and many more...

Upvotes: 4

Wayne Celestin
Wayne Celestin

Reputation: 159

Try adding optional chaining to your code:

<Card.Img variant='top' src={data[0]?.image}/>

Upvotes: -1

Related Questions