What is useEffect and when should it be used?
Updated May 6, 2026
Short answer
useEffect is a React Hook that allows functional components to perform side effects after rendering. It is used for tasks that interact with systems outside React, such as fetching data, updating the document title, setting timers, or subscribing to events. It should be used when a component needs to synchronize with an external system rather than for simple calculations or state updates.
Deep explanation
In React, components are mainly responsible for describing what the UI should look like based on state and props. However, applications often need to perform actions outside the normal rendering process. These actions are called side effects.
Examples of side effects include:
- Fetching data from an API
- Setting up or removing event listeners
- Starting or stopping timers
- Updating browser APIs
- Connecting to external services
- Synchronizing with external state
useEffect allows developers to run this logic after React updates the screen.
Basic syntax
useEffect(() => { // Side effect code
return () => { // Cleanup code (optional) };}, [dependencies]);The function passed to useEffect runs after rendering. The optional cleanup function runs before the effect runs again or when the component is removed from the UI.
---
When should useEffect be used?
1. Fetching data
A common use case is loading data when a component appears.
useEffect(() => { fetchUsers();}, []);The empty dependency array means the request runs once after the initial render.
---
2. Updating external systems
For example, updating the browser title:
useEffect(() => { document.title = "Dashboard";}, []);React updates the UI, and the effect synchronizes the browser document title.
---
3. Setting up subscriptions or event listeners
Example:
useEffect(() => { function handleResize() { console.log(window.innerWidth); }
window.addEventListener("resize", handleResize);
return () => { window.removeEventListener("resize", handleResize); };}, []);The cleanup function removes the listener when the component unmounts, preventing memory leaks.
---
4. Running logic when a value changes
The dependency array controls when the effect runs.
useEffect(() => { console.log("User changed:", userId);}, [userId]);This effect runs:
- After the first render.
- Whenever
userIdchanges.
---
Dependency array behavior
No dependency array
useEffect(() => { console.log("Runs after every render");});The effect runs after every render.
This should be avoided unless the behavior is intentionally needed because it can cause unnecessary work.
---
Empty dependency array
useEffect(() => { console.log("Runs once");}, []);The effect runs after the component mounts and cleanup runs when it unmounts.
---
Dependencies included
useEffect(() => { console.log(count);}, [count]);The effect runs whenever count changes.
---
When should useEffect NOT be used?
Many developers overuse useEffect. It should not be used for things that can be calculated during rendering.
Avoid this:
useEffect(() => { setFullName(firstName + " " + lastName);}, [firstName, lastName]);This creates unnecessary state and an extra render.
Instead:
const fullName = firstName + " " + lastName;Use normal JavaScript for derived values.
---
Cleanup in useEffect
Some effects create resources that must be released.
Examples:
- Timers
- Event listeners
- WebSocket connections
- Subscriptions
Example:
useEffect(() => { const timer = setInterval(() => { console.log("Running"); }, 1000);
return () => { clearInterval(timer); };}, []);The cleanup function ensures the timer does not continue running after the component is removed.
---
Execution lifecycle
A typical useEffect flow:
- Component renders.
- React updates the UI.
- React runs the effect.
- If dependencies change:
- React runs the previous cleanup.
- React runs the updated effect.
- When the component unmounts:
- React runs the cleanup function.
Real-world example
A user profile page needs to fetch user details whenever the selected user ID changes.
import React, { useEffect, useState } from "react";
function UserProfile({ userId }) { const [user, setUser] = useState(null);
useEffect(() => { async function loadUser() { const response = await fetch( `https://api.example.com/users/${userId}` ); const data = await response.json();
setUser(data); }
loadUser(); }, [userId]);
return ( <div> {user ? user.name : "Loading..."} </div> );}In this example:
- The API request runs when the component first loads.
- The request runs again if
userIdchanges. - The component stays synchronized with external data.
Common mistakes
- * Using `useEffect` for simple calculations that can be done during rendering.
- * Forgetting dependencies in the dependency array, causing stale values.
- * Adding too many dependencies without understanding why they trigger rerenders.
- * Updating state inside an effect that depends on the same state, causing infinite loops.
- * Forgetting cleanup for timers, subscriptions, and event listeners.
- * Using `useEffect` as a replacement for every event handler.
Follow-up questions
- What is the difference between useEffect and rendering logic?
- What happens if the dependency array is empty in useEffect?
- Why do we need cleanup functions in useEffect?
- What causes an infinite loop in useEffect?
- What is the difference between useEffect and useLayoutEffect?