React Hooks have revolutionized the way developers handle state and side effects in functional components. One of the most commonly used hooks is useEffect, which allows you to perform side effects in a React component. However, its dependency array (deps
array) often confuses developers, especially when dealing with an empty array ([]
).
In this guide, we will explore what exhaustive dependencies are, how they relate to empty dependency arrays, and best practices to avoid common pitfalls.
What is the useEffect
Hook?
The useEffect
hook is used to handle side effects in React functional components, such as:
- Fetching data from an API
- Subscribing to events
- Updating the DOM
- Managing timers
Basic Syntax
import { useEffect } from "react";useEffect(() => {console.log("Effect runs");});
By default, useEffect
runs after every render, which can lead to unnecessary re-executions. To control when it runs, we use the dependency array.
Understanding the Dependency Array (deps
)
The second argument of useEffect
is the dependency array. It determines when the effect should re-run.
1. No Dependency Array (Runs After Every Render)
useEffect(() => {console.log("Effect runs after every render");});
2. Empty Dependency Array (Runs Only Once)
useEffect(() => {console.log("Effect runs only on mount");}, []);
3. With Dependencies (Runs When Dependencies Change)
const [count, setCount] = useState(0);useEffect(() => {console.log(`Effect runs when count changes: ${count}`);}, [count]);
What Does an Empty Dependency Array ([]
) Mean?
When you pass an empty array ([]
) as the dependency array, React runs the effect only once, after the initial render.
Example: Fetching Data on Mount
useEffect(() => {fetch("https://api.example.com/data").then((response) => response.json()).then((data) => console.log(data));}, []);
In this case, the API call happens only once, when the component mounts.
React Hooks Exhaustive Deps Rule
The exhaustive-deps rule is part of React’s ESLint rules to ensure that all dependencies required by useEffect
are listed in the dependency array.
Example of a Common Warning
const [count, setCount] = useState(0);useEffect(() => {console.log(count); // Warning: React Hook useEffect has a missing dependency: 'count'.}, []);
The warning suggests that count
should be added to the dependency array.
Corrected Version
useEffect(() => {console.log(count);}, [count]);
The exhaustive-deps rule helps prevent bugs caused by missing dependencies.
When to Use an Empty Dependency Array ([]
)?
✅ Good Use Cases
- Fetching data once on mount
- Subscribing to an event listener on mount
- Running a setup function once
Example: Setting up an event listener once
useEffect(() => {window.addEventListener("resize", handleResize);return () => window.removeEventListener("resize", handleResize);}, []);
❌ Bad Use Cases
- Relying on stale state inside the effect
- Skipping necessary dependencies to avoid re-executions
Example of a bug due to missing dependencies:
useEffect(() => {console.log(count); // Stale value}, []);
This code will always log 0
, even if count
updates.
Alternative Approaches
1. Using useRef
for Values That Should Not Trigger Re-Renders
If you need to store a value without re-triggering useEffect
, use useRef
.
const countRef = useRef(0);useEffect(() => {console.log(countRef.current); // Does not trigger re-renders}, []);
2. Using useCallback
to Avoid Unnecessary Dependencies
If a function is causing useEffect
to re-run, wrap it in useCallback
.
const fetchData = useCallback(() => {console.log("Fetching data...");}, []);useEffect(() => {fetchData();}, [fetchData]); // No infinite loop
3. Using useMemo
to Prevent Unnecessary Recalculations
For expensive calculations, use useMemo
to cache results.
const computedValue = useMemo(() => {return expensiveCalculation();}, []);
Common Mistakes and How to Fix Them
Mistake 1: Using Empty Dependency Array Incorrectly
useEffect(() => {console.log(count); // Stale value}, []);
Fix: Add count
to the dependencies.
useEffect(() => {console.log(count);}, [count]);
Mistake 2: Ignoring ESLint Warnings
Ignoring exhaustive-deps warnings can lead to unexpected behavior.
Mistake 3: Unintended Infinite Loops
useEffect(() => {setCount(count + 1); // Infinite loop}, [count]);
Fix: Use a function inside setCount
.
useEffect(() => {setCount((prevCount) => prevCount + 1);}, []);
Summary
useEffect([])
runs only once on mount.- The exhaustive-deps rule ensures all dependencies are listed.
- Empty dependency arrays should only be used for effects that should not re-run.
- Use
useRef
,useCallback
, anduseMemo
to manage dependencies efficiently.
Understanding React Hooks exhaustive deps and empty arrays will help you avoid bugs and write more efficient React applications.