What is useEffect
?
useEffect
is a React hook that allows you to perform side effects in function components. Side effects are tasks that interact with the outside world, like:
- Fetching data from an API
- Setting up subscriptions (e.g., WebSockets)
- Updating the document title
- Changing some data outside the React component (like
localStorage
)
In simple terms, useEffect
lets your component do something after it renders or when something changes.
Basic Syntax
useEffect(() => {
// Code to run after the component renders or updates
});
This useEffect
runs after every render by default.
Common Use Cases for useEffect
:
- Fetching data from an API
- Subscribing to an event or updating an external system
- Cleaning up resources (like clearing timers)
Example 1: Basic Usage
Let’s start with an example where we update the page title with the current counter value.
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// Using useEffect to update the document title
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Counter;
How does this work?
-
useEffect
runs after every render: Every time you click the button and the count changes,useEffect
runs and updates the document title.
Example 2: Controlling When useEffect
Runs (Dependency Array)
Sometimes you don’t want the useEffect
to run on every render, only when certain values change. You can control this using the dependency array.
useEffect(() => {
// Code to run after component renders or when dependencies change
}, [dependency1, dependency2]);
- If one of the dependencies in the array changes,
useEffect
will run again. - If the array is empty
[]
,useEffect
will only run once when the component first renders.
Example: Only Run useEffect
Once
import React, { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
// This useEffect will only run once when the component first renders
useEffect(() => {
console.log('Component mounted!');
}, []); // Empty dependency array
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;
In this example:
- The
useEffect
runs only once when the component mounts (i.e., when it is first added to the page). - It won’t run again, even if you click the button and update the
count
value.
Example 3: Using useEffect
with Cleanup
Sometimes, components set up subscriptions or timers that need to be cleaned up when the component is removed (or unmounted). You can do this with useEffect
by returning a cleanup function.
Example: Cleaning up a Timer
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// Cleanup function to clear the interval when the component unmounts
return () => clearInterval(interval);
}, []); // Empty dependency array to run only on mount/unmount
return (
<div>
<p>Timer: {seconds} seconds</p>
</div>
);
}
export default Timer;
How does this work?
-
Set up a timer: The
setInterval
function runs every second and updates theseconds
state. -
Cleanup: When the component is removed,
clearInterval
is called to stop the timer. The cleanup function is returned insideuseEffect
.
Summary of useEffect
Behavior:
- Run on every render: If you don't include a dependency array.
useEffect(() => {
// Runs after every render
});
- Run only once (when the component mounts):
useEffect(() => {
// Runs only once, after the initial render
}, []);
- Run when certain values change:
useEffect(() => {
// Runs when `count` changes
}, [count]);
- Cleanup: Return a cleanup function to stop or remove things (like timers or subscriptions) when the component unmounts.
useEffect(() => {
// Code to set up something
return () => {
// Code to clean up (runs when component unmounts)
};
}, []);
Key Points:
- useEffect lets you perform side effects in your components.
- By default, it runs after every render, but you can control when it runs using a dependency array.
- Always remember to clean up resources like timers or subscriptions in the return function of
useEffect
to avoid memory leaks.
The lesson I provided covers the core concepts of useEffect
, but there's more to learn about it, especially in advanced cases. Here's a list of additional details that can be helpful to understand useEffect
thoroughly:
Additional Key Concepts for useEffect
-
Multiple
useEffect
Hooks:- You can have more than one
useEffect
in a single component to handle different side effects independently.
- You can have more than one
useEffect(() => {
console.log("Effect 1");
}, []);
useEffect(() => {
console.log("Effect 2");
}, []);
- These will run separately, allowing you to organize your side effects better.
-
Conditional Logic Inside
useEffect
:- You can put conditional logic inside
useEffect
to only perform side effects based on certain conditions.
- You can put conditional logic inside
useEffect(() => {
if (someCondition) {
// Execute side effect only if condition is met
}
}, [someCondition]);
-
Handling Async Code:
-
useEffect
doesn't directly support async functions, but you can call async functions inside it. This is common for data fetching.
-
useEffect(() => {
const fetchData = async () => {
const result = await fetch('https://api.example.com/data');
console.log(result);
};
fetchData();
}, []);
-
Use with Dependencies: Pitfalls and Solutions:
- Always ensure that all dependencies (variables or state) used inside the
useEffect
are listed in the dependency array. This prevents stale closures, where the effect uses old values instead of updated ones.
- Always ensure that all dependencies (variables or state) used inside the
useEffect(() => {
console.log(someVar); // If `someVar` changes, the effect should re-run
}, [someVar]);
- Pitfall: Missing a dependency can lead to bugs, like the effect not re-running when it should.
- Solution: Use ESLint's React rules, which will warn you about missing dependencies.
-
Re-running the Effect:
-
useEffect
re-runs if any dependency changes. If you need a side effect that runs only once on mount or only when certain values change, manage it with the dependency array.
-
-
Common Use Cases for Cleanup:
-
Event listeners: Adding and removing event listeners (e.g.,
window.addEventListener
). - WebSocket connections: Starting a WebSocket connection and closing it.
- Timers: Clearing timeouts/intervals when the component unmounts.
-
Event listeners: Adding and removing event listeners (e.g.,
-
Performance Considerations:
-
useEffect
can potentially cause performance issues if not used properly. For example, running an expensive operation or a data-fetching task on every render may slow down the app. - Optimize by using dependency arrays and breaking complex logic into multiple effects.
-
-
Execution Order:
- Effects run after the render but before the DOM is painted.
- Cleanup functions (if any) run before the component is removed from the UI or before the next effect runs.
-
React Strict Mode and Double Execution:
- In React's strict mode (used in development),
useEffect
runs twice (mount/unmount and then mount again) to help identify potential issues. This doesn’t happen in production.
- In React's strict mode (used in development),
-
Handling Race Conditions in Async Effects:
- If you trigger an API call within
useEffect
, but the component unmounts before the request completes, you could end up updating the state of an unmounted component (causing a memory leak). -
Solution: Abort the API request or use a
cancelled
flag.
useEffect(() => { let isCancelled = false; const fetchData = async () => { const response = await fetch('https://api.example.com'); if (!isCancelled) { // Update state here only if the component is still mounted } }; fetchData(); return () => { isCancelled = true; // Cleanup to prevent updating unmounted component }; }, []);
- If you trigger an API call within
However, for complete mastery of useEffect
, you should also understand:
- Handling async operations and race conditions
- Performance considerations in larger apps
- Advanced use cases with multiple
useEffect
hooks and complex state - React's behavior in strict mode (e.g., double execution in dev mode)
Top comments (0)