useEffect for handling side effects

Mar 17, 2020 路 React, useEffect 路 9 minute read

Introduction

React offers several hooks and one you'll encounter almost certainly, even when building the most basic app, is the useEffect hook.

However, it is not only very commonly used, it's also quite challenging to really grasp it.

Although there are many other blog posts explaining this hook, I still write my own because I am sure it will help me to grasp it even better and I hope it does the same for you too.

Side effects

The useEffect hook is for handling side effects of React components. But what are side effects?

In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, that is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation.

-- Wikipedia

This general definition is very applicable to React components. It describes that next to side effects, there are also main effects. So before going to side effects, the question is: What is the main effect of React components?

Well, a React component actually has two main effects, the first is rendering UIs, i.e. defining what the component looks like in the browser, which is expressed in JSX.

The second one is handling events, which are triggered by the user that interacts with your app. Handling these events most of the times result in a state update, which in turn causes a render again.

This cycle of rendering, handling events, updating state, rendering again etc. is the common lifecycle of most React components. It makes them pure: Given the same input, you always get the same output. This is how you make reusable and testable UI components. They have no behavior other than rendering and handling user events.

However, to make your app really dynamic and useful, you often need to fetch and send data from/to a server. Or maybe you need to start a timer which does something recurring. If these kind of things won't be triggered by a user (event), it is a side effect for a React component.

Do You Haz Teh Codez?

Well, certainly. Let's create a very contrived and simple React app so we can really focus on side effects and useEffect. Our app consists of one component:

function App() {
  return <h1>Hello World</h1>;
}

The App component gets rendered by our app and no matter how often we call it, it always returns a heading containing the text Hello World.

Let's add a button to keep track of how many times it was clicked and display that value. This makes our component dynamic, so we need to use the useState hook to make sure React will always render the current clicked value:

function App() {
  const [clicked, setClicked] = useState(0);
  return (
    <>
      <h1>Hello World</h1>
      <p>{clicked}</p>
      <button onClick={() => setClicked(clicked + 1)}>Click Me</button>
    </>
  );
}

What we've implemented here is a regular UI component. It renders some kind of UI and as soon as the user clicks the button, clicked is incremented and the component is rendered again. It is still pure, it always returns the same UI. Even when you click the button, the UI is very predictable.

Let's add a side effect

While our app is really cool already, we decide to spice it up a bit. Instead of the Hello World text, we want to display some random text we fetch from an API.

To keep it simple, it doesn't matter which API we call and whether that API is REST or GraphQL. What does matter though is how and when we call the API.

Every time someone opens our app, the App component's main effect is executed, which renders the UI. What we also want to do is call the API to fetch the text. This is a side effect, so we'll use the useEffect hook in our component.

The useEffect hook is a function, provided by React. For each side effect you want to implement, you call this function, inside your component. You pass at least one argument and that is the function that contains the implementation of your actual side effect. In our case that is calling the API to fetch a random text. What React will do is call this function argument after each render.

In the code below, I've added the useEffect call and pass the getTextFromApi function. Every time the App component is rendered the getTextFromApi is called, even though it doesn't do anything yet:

function App() {
  const [clicked, setClicked] = useState(0);

  useEffect(function getTextFromApi() {
    //TODO: call the API...
  });

  // <--- unchanged return statement ommitted ---> //
}

It is common to pass an arrow function to useEffect, but by explicitely naming the function getTextFromApi like I do here, it is much clearer what this side effect implementation actually does.

Calling an API inside useEffect

The current Hello World text must be replaced by a text coming from the API, so we'll create a state variable for this called text and the function setText to update it. In the JSX we render this text variable instead of the hard-coded Hello World.

Furthermore, we'll import the getRandomText function which does the actual API call.

Finally, inside the getTextFromApi function. inside the useEffect, we call getRandomText and then setText to update the text state.

Now, our code looks like this:

function App() {
  const [clicked, setClicked] = useState(0);
  const [text, setText] = useState();

  useEffect(function getTextFromApi() {
    getRandomText().then((text) => setText(text));
  });

  return (
    <>
      <h1>{text}</h1>
      <p>{clicked}</p>
      <button onClick={() => setClicked(clicked + 1)}>Click Me</button>
    </>
  );
}

Every time the component is rendered, text is updated with the text coming from the API. This happens when the component is rendered for the first time but also when clicking the button, because then the clicked state is updated which also causes a render.

However, there is no reason to call the API after each button click. We only want to call it when the component is rendered for the first time.

A great blog post about fetching data with useEffect is Robin Wieruch's How to fetch data with React Hooks?. It also covers working with async and await.

The dependency array

Now that we know how to handle and implement side effects with the first argument to the useEffect call, let's look at controlling when to execute these side effects. This is done by supplying a second argument to useEffect, which is the so-called dependency array.

This array must contain all variables where the side effect implementation in the first useEffect argument depends on. In other words: which variables are used in the implementation.

Possible candidates for such a dependency are variables that are an argument to the component function, better known as props, and variables (so functions too) that are defined inside the component.

The getRandomText function we use in our effect is imported, so it is defined outside the component, and therefore is not a dependency. If it would be a local function, it would have been a dependency.

So our side effect has no dependencies, but how can we use the dependency array to make sure the effect is only called once?

Before the side effect inside useEffect is executed, React checks first whether the variables in the dependency array have a different value since the previous render. If not, it is not necessary to execute the effect again, because executing the effect with the same values would yield the same result anyway.

So to make sure an effect is only executed once, you just make sure the values inside the dependency array never change. And the easiest way to achieve that is to pass an empty dependency array because an empty array will never change! 馃槃

So the only thing we have to add in our code is an empty array as the second argument to our useEffect call:

useEffect(
  function getTextFromApi() {
    getRandomText().then((text) => setText(text));
  },
  [] // <=== Empty dependency array
);

A useEffect with dependencies

Let's change our app so I can demonstrate how useEffect works when you do have a dependency.

Instead of getting a random text for the API I will call the so-called Numbers API. This API returns trivia about numbers. I'll import the getNumberText function which handles calling the Numbers API and I remove the import of getRandomText.

Inside my getTextFromApi function I now call getNumberText and pass a number, which is the clicked state variable I already had.

If I would run my app now the effect would only be called the first time, while I also want to call it when clicked changes. This is the moment to add the clicked variable to the dependency array:

useEffect(
  function getTextFromApi() {
    getNumberText(clicked).then((text) => setText(text));
  },
  [clicked]
);

Closing note

This post covered the basics of handling side effects in React with the useEffect hook. I learned a lot from it and I hope you did too.

Want a real deep dive into useEffect?

Then read Dan Abramov's very long blog post (or mini-book, as he calls it) A Complete Guide To useEffect.

Please contact me on Twitter if you have questions or remarks.

Share on TwitterDiscuss on TwitterEdit on GitHub