Devtrium

What are dependency arrays in React?

August 08, 20215 min read

Introduction

If you've worked with functional components and hooks in React, you've probably already heard the term "dependency array". But are you comfortable with this concept?

What are dependency arrays? How to use dependency arrays? What should be included in dependency arrays? I'll strive to answer this question and more!

What is a dependency array

Dependency arrays are a concept that is tightly coupled to hooks in React (thus also to function components).

Some hooks, like useEffect and useCallback have 2 arguments. The first one is a callback (a function), and the second one is the dependency array. It takes the form of an array of variables.

In the following example, [counter] is the dependency array of the useEffect hook:

useEffect(() => {
  console.log('Counter has value: ', counter);
}, [counter]);

The React hooks that have dependency arrays are:

  • useEffect
  • useLayoutEffect
  • useCallback
  • useMemo
  • useImperativeHandle (which is a super rare hook that you almost never use and shouldn't care too much about)

What is a dependency array used for?

The dependency array basically tells the hook to "only trigger when the dependency array changes". In the above example, it means "run the callback every time the counter variable changes".

If you have multiple elements in a dependency array, the hook will trigger if any element of the dependency array changes:

useEffect(() => {
  // this will run if `counter1` OR `counter2` changes
  console.log('Either counter1 or counter2 changed (or both');
}, [counter1, counter2]);

You might ask, what does it mean for the hook to "trigger" every time an element of the dependency array changes?

Well, it depends on the hook. For the useEffect hook, it means running the callback. For the useCallback hook, it means changing the function being returned by the hook. Same for useMemo, but with a value.

If you want more info on those hooks, reach out on Twitter to poke me about it, and I'll write an article!

Empty dependency array

As I said, the dependency array controls when the hook triggers. So what happens when the dependency array is empty?

It simply means that the hook will only trigger once when the component is first rendered. So for example, for useEffect it means the callback will run once at the beginning of the lifecycle of the component and never again.

useEffect(() => {
  console.log('I will run only once');
}, []);

This is a very common pattern when you want to do something at the beginning of the lifecycle of a component, for example to do some data fetching.

useEffect(() => {
  // this will run only once when the component first renders
  fetch('https://yourapi.com');
}, []);
Info

Doing data fetching in a useEffect hook is a bit more involved than the code above, because it involves calling an async function in the callback. I have a whole article on doing async calls in useEffect hooks!!

What should be put in the dependency array?

It's a pretty simple rule, made a bit harder by some exceptions.

The rule is: if any variable is used inside the hook but is defined outside of it, then it should go in the dependency array. That goes both for simple variables as well as for functions.

import { useEffect } from 'react';

const ExampleComponent = () => {
  const width = 200;

  const printToConsole = (value) => {
    console.log(value);
  };

  useEffect(() => {
    printToConsole(width);
  }, [width, printToConsole]);

  return <p>Hello World!</p>;
};
Info

This example will actually lead to a bug, because the printToConsole function isn't wrapped in useCallback. That will lead to useEffect triggering on every render of ExampleComponent! I'll show a better way to do this in the next section.

As you can see in the above (quite contrived) example, both the variable width and the function printToConsole are used in the useEffect hook, and thus need to go in the array.

So the rule is pretty simple, but as I said there's some exceptions.

Values defined outside of the component

If a value is defined outside of a component, then it is fixed and won't change when the app is running. Thus React doesn't need you to add it to the dependency array.

In the last example, there was actually no need to define width and printToConsole inside the component like I did. The following is better:

import { useEffect } from 'react';

const width = 200;

const printToConsole = (value) => {
  console.log(value);
};

const ExampleComponent = () => {
  useEffect(() => {
    printToConsole(width);
  }, []);

  return <p>Hello World!</p>;
};

It's good practice to put everything that can go outside of a component outside of it. Constants (like width) and utility functions (like printToConsole) don't need to be defined inside the component.

Already optimized functions

As we saw, the goal of the dependency array is to make the hook trigger when one of the values changes. There's no point putting stuff in there that won't change.

And there's some values that React knows for sure won't change, because React itself makes sure of that. An example of this behavior is the setter function returned by a useState hook:

const [counter, setCounter] = useState(0);

The setCounter function is optimized by React and won't change. So even if it's used in a hook with a dependency array you don't need to add it to the dependency array.

import { useState, useEffect } from 'react';

const ExampleComponent = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    setCounter(10);
  }, []);

  return <p>Counter is: {counter}</p>;
};

The same is true for the dispatch function returned by the useReducer hook.

Tip

If setCounter was passed as a prop from a higher component, you'd have to include it in the dependency array because React doesn't know where it comes from. It won't trigger the hook though, so it's safe to do so.

Refs

Refs can be quite difficult to wrap one's head around, and it probably deserves a whole article by itself.

But as far as dependency arrays are concerned, be aware that it's useless to put a ref in a dependency array. Without getting too much into the details, it's because a ref changing value won't trigger a re-render of the component, so the hook won't trigger, regardless of its dependency array (dependency arrays are only checked when the component re-renders).

Tip

Although putting the ref in the dependency array won't do anything, do not put ref.current inside the dependency array! This will lead to bugs which can be very hard to find!

Use es-lint to help you!

That's a lot of exceptions, they can be hard to keep in mind. But worry not, ESLint is there to help you out (You are using a linter right? If not, you really should!).

The rule react-hooks/exhaustive-deps warns you when you're doing something wrong with your dependency array. If you want more info, you can check out the official React documentation!

Any more exceptions?

Nope! Well, nearly. There's some rare cases where ESLint will tell you to put something in a dependency array and you shouldn't do it. But those are usually advanced use cases where you know what you're doing anyway. If you're starting out, the general rule and the exceptions above are enough to get you going quite a way!

Careful with what you put in your dependency array

As a change in the dependency array means a trigger in your hook, you have to be careful with what you put in your dependency array. In particular, functions defined inside a component should be wrapped with useCallback, and values with useMemo!

Wrap up

That was longer than I expected! But this is a very important subject in React, one that also causes a lot of confusion among beginner and even intermediate level developers.

I hope I helped you to understand this subject better. In any case, the best way to really understand it through practice. So the next time you write a dependency array, take a moment to think about what it's doing and what should go into it!

Package versions at the time of writing

Did you enjoy this article?

If so, a quick share on Twitter could really help out!