DEV Community

Juhana Jauhiainen
Juhana Jauhiainen

Posted on • Updated on • Originally published at juhanajauhiainen.com

Making API calls in React useEffect

useEffect is a hook added in React 16.8. It allows you to perform side effects in function components. This means you can update things outside of React based on your propsand state. Fetching data when the component state changes, changing the page <title> or connecting to a WebSocket server are all examples of side effects that can be done with useEffect.

As an example, we are going to build a component which fetches data from Cat Facts API and displays the received facts as a list. Finally we'll add buttons to select the animal we want facts about.

Let's start with a simple component which prints a message to console when the it is mounted.

function AnimalFactsList(props) {
  useEffect(() => {
    console.log("Hello from useEffect!");
  })
  return <div></div>
}
Enter fullscreen mode Exit fullscreen mode

This seems to be working. "Hello from useEffect!" is printed to console when the component mounts. In fact React runs the function we supplied to useEffect every time it renders our component.

Next we will add state variable to hold the data we fetch, a API call for fetching it and display the results as a list of <p> elements.

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

function AnimalFactsList(props) {
    const [animalFacts, setAnimalFacts] = useState([]);

    useEffect(() => {
        fetch("https://cat-fact.herokuapp.com/facts/random?animal_type=cat&amount=5")
            .then(response => response.json())
            .then(response => setAnimalFacts(response));
  });

  const facts = animalFacts.map(fact => <p key={fact._id}>{fact.text}</p>);

    return (
        <div>
            { facts }
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

If you run this you'll see something like this.

API call causing render loop

Uh oh.. Something is wrong here. The API is being called over and over and over again.

Remember, React is running our useEffect function every time it renders our component. The problem is.. we are changing the state of the component in our side effect function! And since React renders our component again when it's state has changed, we have created render loop.

From React the documentation we find out we can skip running the effect by giving useEffecta second argument which defines the effects dependencies.

For now we want to run the effect only when the component mounts. This is what the documentation has to say.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

So let's add [] as the second parameter to useEffect.

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

function AnimalFactsList(props) {
    const [animalFacts, setAnimalFacts] = useState([]);

    useEffect(() => {
        fetch("https://cat-fact.herokuapp.com/facts/random?animal_type=${animalType}&amount=5")
            .then(response => response.json())
            .then(response => setAnimalFacts(response));
  }, []);

  const facts = animalFacts.map(fact => <p key={fact._id}>{fact.text}</p>);

    return (
        <div>
            { facts }
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Now the API is getting called only once when our component is mounded.

Next we'll add the ability to change the animal we're downloading facts about. We will add a few buttons, a new state variable and use the state variable in our API call.

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

function AnimalFactsList(props) {
  const [animalFacts, setAnimalFacts] = useState([]);
  const [animalType, setAnimalType] = useState("cat");

  useEffect(() => {
    fetch(
      `https://cat-fact.herokuapp.com/facts/random?animal_type=${animalType}&amount=5`
    )
      .then(response => response.json())
      .then(response => {
        setAnimalFacts(response);
      });
  }, [animalType]);

  const facts = animalFacts.map(fact => <p key={fact._id}>{fact.text}</p>);

  return (
    <div>
      <h1>Here's some facts about {animalType}s</h1>
      {facts}
      <button onClick={() => setAnimalType("cat")}>Cat</button>
      <button onClick={() => setAnimalType("dog")}>Dog</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice we've also added the new state variable animalTypeas a dependency to our effect. If we didn't do that the effect would be called only once when the component mounts but not after the animalTypestate variable changes.

This is a key concept of useEffect.

You must add all the variables (props/state) the effect uses to the dependencies. If the dependencies are incorrect, the effect won't run when it's supposed to and the state variables inside the effect will have their initial values.

Checkout the full code for this example at codesandbox.io

To better understand useEffectand how functional components work in React, I highly recommend reading Dan Abramovs excellent blog post A Complete guide to useEffect

Top comments (0)