What are dependency arrays in React?
August 08, 2021 • 5 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');
}, []);
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>;
};
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.
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).
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!