useEffect
useEffect in React explains synchronizing components with external systems, dependency arrays, cleanup, Strict Mode, and avoiding Effects for ordinary calculations or event responses.
useEffect is a React Hook used to synchronize a component with an external system.
It is commonly used when a component needs to perform work that is caused by rendering but is not itself part of calculating the JSX.
Examples include:
- Fetching data from an API
- Updating the document title
- Connecting to a WebSocket
- Subscribing to an event
- Starting and cleaning up a timer
- Listening to browser events
- Synchronizing with browser APIs
- Connecting to third-party libraries
The most important idea is:
useEffect is primarily about synchronization with systems outside React.
Why Do We Need useEffect?
A React component should ideally describe:
Props + State↓Render↓UI
But applications also need to interact with systems outside React:
React component│├── Browser API├── Network├── Timer├── WebSocket├── Event listener└── Third-party library
These interactions are called side effects.
For example:
document.title = "Dashboard";
This changes something outside the component's rendered JSX.
Or:
window.addEventListener("resize", handleResize);
This creates an external subscription.
useEffect provides a structured way to synchronize these external systems with the component.
Importing useEffect
Import it from React:
import { useEffect } from "react";
It can be used alongside other Hooks:
import { useState, useEffect } from "react";
Basic Syntax
The basic structure is:
useEffect(() => {// Effect logic});
A dependency array can be provided:
useEffect(() => {// Effect logic}, []);
Or:
useEffect(() => {// Effect logic}, [dependency]);
The dependency array tells React which reactive values the effect depends on.
The Three Common Forms
There are three important forms to understand.
1. No Dependency Array
useEffect(() => {console.log("Effect ran");});
The effect is associated with every completed render.
This is usually not what you want for most effects because it can run more often than necessary.
2. Empty Dependency Array
useEffect(() => {console.log("Effect ran");}, []);
The effect has no reactive dependencies.
It runs after the component's initial mount.
In development, React Strict Mode may intentionally run setup and cleanup an additional time to help reveal bugs. Therefore, do not rely on an effect running exactly once as a universal guarantee.
3. Dependency Array With Values
useEffect(() => {console.log("User changed:", userId);}, [userId]);
The effect is synchronized when userId changes between renders.
The mental model is:
Render↓Compare dependencies↓Dependency changed?│├── No → Skip effect│└── Yes → Run effect
A Simple Example: Document Title
import { useEffect, useState } from "react";function Counter() {const [count, setCount] = useState(0);useEffect(() => {document.title = `Count: ${count}`;}, [count]);return (<button onClick={() => setCount(previous => previous + 1)}>Count: {count}</button>);}export default Counter;
The relationship is:
count changes↓component renders↓effect runs↓document.title is synchronized
Here, document.title belongs to the browser environment, not the React component tree.
Rendering vs Effects
One of the most important concepts is separating rendering from effects.
Rendering should calculate the UI:
function User({ name }) {return <h1>Hello, {name}</h1>;}
An effect performs synchronization:
useEffect(() => {document.title = name;}, [name]);
Think:
Render↓Calculate JSXEffect↓Synchronize external system
An effect is not another place to calculate ordinary values that could have been calculated during rendering.
What Is a Side Effect?
A side effect is an operation that interacts with something outside the component's rendering calculation.
Examples:
document.title = "Hello";
localStorage.setItem("theme", "dark");
window.addEventListener("resize", handleResize);
fetch("/api/users");
connection.connect();
These operations interact with external systems.
By contrast:
const fullName = `${firstName} ${lastName}`;
is just a calculation.
It does not need an effect.
The Synchronization Mental Model
Instead of thinking:
Run this code after rendering.
prefer:
Keep this external system synchronized with the current React state and props.
For example:
useEffect(() => {document.title = `Cart (${cartItems.length})`;}, [cartItems.length]);
The effect is responsible for keeping the browser tab title synchronized with the cart state.
This mental model leads to better effect design.
Dependency Arrays
Dependencies are one of the most important parts of useEffect.
Consider:
useEffect(() => {console.log(userId);}, [userId]);
The effect reads userId, so userId belongs in the dependency array.
If another reactive value is read by the effect:
useEffect(() => {console.log(userId, theme);}, [userId, theme]);
both values are dependencies.
A useful rule is:
If an effect uses a reactive value from the component, that value generally needs to be represented in the dependency list.
React's Hooks linting tools can help identify missing dependencies.
What Counts as a Reactive Value?
Values declared inside a component can be reactive:
function Profile({ userId }) {const [name, setName] = useState("");useEffect(() => {console.log(userId, name);}, [userId, name]);// ...}
Here:
userId → propname → state
Both can change between renders.
They therefore affect when the synchronization should be recalculated.
Dependency Comparison
React compares dependency values between renders.
For example:
useEffect(() => {// ...}, [userId]);
If:
Previous userId = 10Current userId = 10
the dependency has not changed.
If:
Previous userId = 10Current userId = 20
the dependency changed.
The effect can then run again.
React uses Object.is semantics when comparing dependency values.
Object and Array Dependencies
Be careful when using objects or arrays as dependencies.
Consider:
function Component({ user }) {useEffect(() => {console.log(user);}, [user]);// ...}
If the user object is recreated with a different reference, React can consider it changed even if the properties contain the same values.
For example:
const user = {name: "John"};
Creating a new object produces a new reference:
const anotherUser = {name: "John"};
Even though the contents look the same, these are different object references.
This is why dependency design matters.
Cleanup Functions
Some effects create subscriptions or resources that need to be removed.
Examples:
- Event listeners
- Timers
- WebSocket connections
- Subscriptions
- Third-party resources
An effect can return a cleanup function:
useEffect(() => {// Setupreturn () => {// Cleanup};}, []);
Event Listener Example
import { useEffect } from "react";function WindowWidth() {useEffect(() => {function handleResize() {console.log(window.innerWidth);}window.addEventListener("resize", handleResize);return () => {window.removeEventListener("resize", handleResize);};}, []);return <p>Resize the browser window.</p>;}
The lifecycle is:
Component mounts↓Effect setup↓Add event listener↓Component remains active↓Component unmounts↓Cleanup↓Remove event listener
The cleanup prevents the component from leaving behind an unnecessary subscription.
Cleanup Before Re-Synchronization
Cleanup is not only for unmounting.
When dependencies change, React can:
Previous effect↓Cleanup previous synchronization↓Render with new values↓Run new effect
For example:
useEffect(() => {const connection = createConnection(roomId);connection.connect();return () => {connection.disconnect();};}, [roomId]);
When roomId changes, the old connection should be cleaned up before synchronizing the new one.
This is one of the most important reasons to return cleanup functions.
Timer Example
import { useEffect } from "react";function Timer() {useEffect(() => {const intervalId = setInterval(() => {console.log("Tick");}, 1000);return () => {clearInterval(intervalId);};}, []);return <p>Timer is running.</p>;}
Without cleanup:
Component↓Creates interval↓Component disappears↓Interval may continue running
With cleanup:
Component↓Creates interval↓Component disappears↓clearInterval()↓Timer stopped
Effect Lifecycle
A useful conceptual lifecycle is:
Component renders↓React commits the UI↓Effect setup runs↓External system synchronized↓Dependencies change?│├── No│└── Yes↓Cleanup previous effect↓Run new effect setup
When the component is removed:
Component unmounts↓Cleanup runs
This is why an effect can be thought of as a synchronization process with a corresponding cleanup process.
Fetching Data With useEffect
A common beginner example is fetching data:
import { useEffect, useState } from "react";function Users() {const [users, setUsers] = useState([]);const [loading, setLoading] = useState(true);useEffect(() => {async function loadUsers() {const response = await fetch("/api/users");const data = await response.json();setUsers(data);setLoading(false);}loadUsers();}, []);if (loading) {return <p>Loading...</p>;}return (<ul>{users.map(user => (<li key={user.id}>{user.name}</li>))}</ul>);}
The flow is:
Component renders↓Effect starts request↓API responds↓setUsers(...)↓setLoading(...)↓Component renders again↓UI displays data
Handling Fetch Errors
Production code should generally handle failures.
import { useEffect, useState } from "react";function Users() {const [users, setUsers] = useState([]);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {async function loadUsers() {try {const response = await fetch("/api/users");if (!response.ok) {throw new Error("Failed to load users");}const data = await response.json();setUsers(data);} catch (error) {setError(error);} finally {setLoading(false);}}loadUsers();}, []);if (loading) {return <p>Loading...</p>;}if (error) {return <p>Unable to load users.</p>;}return (<ul>{users.map(user => (<li key={user.id}>{user.name}</li>))}</ul>);}
This creates three useful UI states:
Loading↓SuccessORError
Fetching Data When a Dependency Changes
Suppose a page displays a selected user:
function UserProfile({ userId }) {const [user, setUser] = useState(null);useEffect(() => {async function loadUser() {const response = await fetch(`/api/users/${userId}`);const data = await response.json();setUser(data);}loadUser();}, [userId]);if (!user) {return <p>Loading...</p>;}return <h2>{user.name}</h2>;}
The dependency:
[userId]
means the effect should synchronize the displayed data whenever the selected user changes.
userId = 10↓fetch user 10userId = 20↓fetch user 20
Race Conditions in Data Fetching
When fetching data based on changing dependencies, an earlier request may finish after a newer request.
Example:
Request A → user 10Request B → user 20B finishes first↓UI shows user 20A finishes later↓UI could incorrectly show user 10
One approach is to ignore stale results using an ignore flag:
useEffect(() => {let ignore = false;async function loadUser() {const response = await fetch(`/api/users/${userId}`);const data = await response.json();if (!ignore) {setUser(data);}}loadUser();return () => {ignore = true;};}, [userId]);
Another approach is to use AbortController when the underlying API supports cancellation.
For larger applications, dedicated data-fetching libraries or framework-level data APIs may provide better caching, cancellation, deduplication, and loading-state management.
AbortController Example
useEffect(() => {const controller = new AbortController();async function loadUser() {try {const response = await fetch(`/api/users/${userId}`,{signal: controller.signal});const data = await response.json();setUser(data);} catch (error) {if (error.name !== "AbortError") {setError(error);}}}loadUser();return () => {controller.abort();};}, [userId]);
The cleanup cancels the previous request when the synchronization is replaced or the component is removed.
Effects Are Not Event Handlers
An event handler responds to a specific interaction:
function handleSubmit() {// User submitted the form}
An effect responds to rendering and dependency changes:
useEffect(() => {// Synchronize external system}, [value]);
Do not automatically move event-specific logic into an effect.
For example, if clicking a button should submit an order:
function handleOrder() {submitOrder();}
is generally clearer than:
useEffect(() => {if (shouldSubmit) {submitOrder();}}, [shouldSubmit]);
When something happens specifically because the user interacted with the UI, an event handler is often the appropriate place.
Effects Are Not for Derived Data
Suppose:
const [firstName, setFirstName] = useState("John");const [lastName, setLastName] = useState("Doe");
Avoid:
const [fullName, setFullName] = useState("");useEffect(() => {setFullName(`${firstName} ${lastName}`);}, [firstName, lastName]);
This introduces unnecessary state and an unnecessary render cycle.
Prefer:
const fullName = `${firstName} ${lastName}`;
The value can be calculated directly during rendering.
This is a very important principle:
Do not use an effect to calculate something that can be calculated during rendering.
Effects Are Not for State-to-State Synchronization
Avoid patterns like:
const [firstName, setFirstName] = useState("");const [lastName, setLastName] = useState("");const [fullName, setFullName] = useState("");useEffect(() => {setFullName(`${firstName} ${lastName}`);}, [firstName, lastName]);
Instead:
const fullName = `${firstName} ${lastName}`;
There is one source of truth rather than three pieces of related state.
When You Might Not Need an Effect
Before writing:
useEffect(() => {// ...}, []);
ask:
What external system am I synchronizing with?
If the answer is nothing, the effect may not be necessary.
Common situations where an effect is often unnecessary:
Transforming data
const sortedUsers = [...users].sort(...);
Calculating a value
const total = price * quantity;
Handling a click
function handleClick() {// ...}
Rendering conditionally
return isLoggedIn ? <Dashboard /> : <Login />;
Resetting state unnecessarily
Sometimes component structure or a key can provide a cleaner reset mechanism.
Synchronizing With Browser APIs
Effects can synchronize React state with browser APIs.
For example:
function PageTitle({ title }) {useEffect(() => {document.title = title;}, [title]);return <h1>{title}</h1>;}
The React UI is:
<h1>{title}</h1>
The browser state is:
document.title
The effect keeps them synchronized.
Local Storage Example
Suppose a theme needs to be persisted:
function ThemePreference({ theme }) {useEffect(() => {localStorage.setItem("theme", theme);}, [theme]);return <p>Current theme: {theme}</p>;}
Here:
React state/prop↓theme↓useEffect↓localStorage
localStorage is an external browser system, so synchronization is a reasonable use of an effect.
Browser Event Example
import { useEffect, useState } from "react";function OnlineStatus() {const [isOnline, setIsOnline] = useState(navigator.onLine);useEffect(() => {function handleOnline() {setIsOnline(true);}function handleOffline() {setIsOnline(false);}window.addEventListener("online", handleOnline);window.addEventListener("offline", handleOffline);return () => {window.removeEventListener("online", handleOnline);window.removeEventListener("offline", handleOffline);};}, []);return (<p>{isOnline ? "Online" : "Offline"}</p>);}export default OnlineStatus;
This demonstrates a common effect pattern:
Setup external subscription↓Respond to external changes↓Update React state↓Render UI↓Cleanup subscription
This pattern can later be extracted into a custom Hook.
WebSocket Example
A component may need to connect to a real-time server.
useEffect(() => {const socket = new WebSocket(`wss://example.com/rooms/${roomId}`);socket.addEventListener("message", handleMessage);return () => {socket.removeEventListener("message", handleMessage);socket.close();};}, [roomId]);
The effect synchronizes the connection with the current roomId.
When the room changes:
Old room↓Cleanup old connection↓New room↓Create new connection
Third-Party Libraries
Effects are often useful when integrating React with libraries that manage their own systems.
For example:
useEffect(() => {const chart = createChart(chartContainer.current);chart.setData(data);return () => {chart.destroy();};}, [data]);
The effect handles the lifecycle of the external chart instance.
This is one of the strongest use cases for effects:
React↕External library
Strict Mode and useEffect
When an application is wrapped in React Strict Mode during development, React may intentionally perform an additional setup → cleanup → setup cycle for effects.
For example:
Development:setupcleanupsetup
This helps reveal effects that are not properly reversible.
If your effect adds a listener:
window.addEventListener("resize", handler);
the cleanup should remove it:
window.removeEventListener("resize", handler);
If your effect opens a connection:
connection.connect();
the cleanup should disconnect it:
connection.disconnect();
The goal is for:
setup → cleanup
to correctly undo the work performed by setup.
Do not add flags simply to force an effect to appear to run once. Fix the effect's setup and cleanup logic instead.
Common useEffect Mistakes
Mistake 1: Missing Dependencies
Incorrect:
useEffect(() => {console.log(userId);}, []);
If the effect is supposed to synchronize with changing userId, omitting it can create stale behavior.
Prefer:
useEffect(() => {console.log(userId);}, [userId]);
Mistake 2: Using Effects for Derived Values
Avoid:
useEffect(() => {setTotal(price * quantity);}, [price, quantity]);
Prefer:
const total = price * quantity;
Mistake 3: Forgetting Cleanup
Incorrect:
useEffect(() => {window.addEventListener("resize", handleResize);}, []);
Prefer:
useEffect(() => {window.addEventListener("resize", handleResize);return () => {window.removeEventListener("resize", handleResize);};}, []);
Mistake 4: Creating Infinite Effect Loops
Consider:
useEffect(() => {setCount(count + 1);}, [count]);
The sequence is:
count changes↓effect runs↓setCount(...)↓count changes↓effect runs again↓...
This can create an infinite update loop.
Always ask whether an effect's state update changes one of the effect's own dependencies.
Mistake 5: Putting Event Logic in Effects
If something should happen because the user clicked a button, prefer:
function handleClick() {saveData();}
rather than creating an intermediate state solely to trigger an effect.
Mistake 6: Using an Effect to Reset Derived State
Avoid unnecessary synchronization such as:
useEffect(() => {setFilteredItems(filterItems(items, search));}, [items, search]);
If filtering is inexpensive, prefer:
const filteredItems = filterItems(items, search);
If the calculation is genuinely expensive, performance-specific tools such as useMemo may be considered—but only when there is a measured need.
Effect Dependency Checklist
When writing an effect, ask:
1. What external system am I synchronizing with?
Examples:
BrowserNetworkTimerWebSocketSubscriptionThird-party library
2. What values does the effect read?
PropsStateVariables declared inside the component
3. Which values should trigger re-synchronization?
Those values generally belong in the dependency array.
4. Does the effect create something that needs cleanup?
If yes, return a cleanup function.
5. Can this logic happen during rendering instead?
If yes, an effect may not be necessary.
6. Is this actually an event response?
If yes, an event handler may be clearer.
Separating Effects
Avoid putting unrelated synchronization processes into one giant effect.
Instead of:
useEffect(() => {document.title = title;localStorage.setItem("theme", theme);connectToChat(roomId);}, [title, theme, roomId]);
consider separate effects:
useEffect(() => {document.title = title;}, [title]);useEffect(() => {localStorage.setItem("theme", theme);}, [theme]);useEffect(() => {const connection = connectToChat(roomId);return () => {connection.disconnect();};}, [roomId]);
Each effect now has one clear synchronization responsibility.
This makes dependency management and cleanup easier to understand.
Effect Logic Should Be Reversible
A useful design principle is:
Setup↓External resource existsCleanup↓External resource removed
Examples:
| Setup | Cleanup |
|---|---|
addEventListener | removeEventListener |
setInterval | clearInterval |
setTimeout | clearTimeout when needed |
connect() | disconnect() |
subscribe() | unsubscribe() |
| Create library instance | Destroy library instance |
Not every effect requires cleanup. Effects that only synchronize a value such as:
document.title = title;
may not need one.
A Complete Synchronization Example
Consider a chat room:
import { useEffect } from "react";function ChatRoom({ roomId }) {useEffect(() => {const connection = createConnection(roomId);connection.connect();return () => {connection.disconnect();};}, [roomId]);return <h2>Room: {roomId}</h2>;}
The effect describes one synchronization process:
Current roomId↓Create connection↓ConnectroomId changes↓Disconnect old room↓Create new connection↓Connect new roomComponent unmounts↓Disconnect
This is the mental model to aim for when designing effects.
useEffect vs useState
useState and useEffect solve different problems.
| Hook | Primary purpose |
|---|---|
useState | Store changing component state |
useEffect | Synchronize with external systems |
Example:
const [count, setCount] = useState(0);useEffect(() => {document.title = `Count: ${count}`;}, [count]);
Here:
useState↓stores countuseEffect↓synchronizes document.title
useEffect vs Event Handlers
Event handler:
function handleSubmit() {saveForm();}
Effect:
useEffect(() => {document.title = title;}, [title]);
Use an event handler when the logic is caused by a specific user action.
Use an effect when the component needs to synchronize an external system with its current state or props.
useEffect vs useMemo
These Hooks have different purposes.
useMemo:
const filteredItems = useMemo(() => filterItems(items, search),[items, search]);
is about caching a calculation.
useEffect:
useEffect(() => {saveToStorage(items);}, [items]);
is about synchronizing with an external system.
Do not use useEffect as a replacement for useMemo.
useEffect vs useRef
useRef stores a value that persists across renders without causing a render when it changes.
For example:
const inputRef = useRef(null);
An effect can use a ref to interact with the DOM:
useEffect(() => {inputRef.current?.focus();}, []);
Here:
useRef↓Access DOM nodeuseEffect↓Perform synchronization after render
useRef will be covered in the next Hook lesson.
Best Practices
1. Start With the External System
Before writing an effect, identify what is outside React that needs synchronization.
2. Keep Each Effect Focused
One synchronization concern per effect is often easier to maintain.
3. Declare Dependencies Honestly
Do not remove dependencies just to make an effect run less often.
Instead, restructure the code if the dependencies reveal a design problem.
4. Always Clean Up Subscriptions
If setup creates a subscription or resource, provide matching cleanup.
5. Avoid Effects for Derived Data
Calculate derived values during rendering.
6. Avoid Effects for User Events
Use event handlers for event-specific actions.
7. Be Careful With Object and Function Dependencies
Unstable references can cause effects to re-run.
Do not automatically add memoization everywhere. First understand why the dependency changes and whether the effect actually needs to synchronize again.
8. Handle Async Work Carefully
Consider loading states, errors, cancellation, and stale responses.
9. Make Effects Safe to Re-run
An effect should correctly handle setup and cleanup without relying on accidental execution counts.
Practical Example: Search Synchronization
Consider a search component:
import { useEffect, useState } from "react";function SearchResults({ query }) {const [results, setResults] = useState([]);const [loading, setLoading] = useState(false);useEffect(() => {let ignore = false;async function search() {setLoading(true);const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);const data = await response.json();if (!ignore) {setResults(data);setLoading(false);}}search();return () => {ignore = true;};}, [query]);if (loading) {return <p>Searching...</p>;}return (<ul>{results.map(result => (<li key={result.id}>{result.title}</li>))}</ul>);}
The effect synchronizes the external search request with the current query.
query changes↓old synchronization cleaned up↓new search starts↓results update↓UI renders
Practical Example: Window Width
import { useEffect, useState } from "react";function WindowSize() {const [width, setWidth] = useState(window.innerWidth);useEffect(() => {function handleResize() {setWidth(window.innerWidth);}window.addEventListener("resize", handleResize);return () => {window.removeEventListener("resize", handleResize);};}, []);return <p>Window width: {width}px</p>;}export default WindowSize;
This example demonstrates:
- State
- Browser events
- Effect setup
- Cleanup
- External system synchronization
Practical Example: Persisting State
import { useEffect, useState } from "react";function ThemeSelector() {const [theme, setTheme] = useState("light");useEffect(() => {localStorage.setItem("theme", theme);}, [theme]);return (<buttononClick={() =>setTheme(previous =>previous === "light" ? "dark" : "light")}>Theme: {theme}</button>);}export default ThemeSelector;
The state is React-managed:
theme
The persistence system is outside React:
localStorage
The effect connects the two.
Debugging useEffect
When an effect behaves unexpectedly, inspect:
1. Does it run too often?
Check:
- Dependency values
- Object references
- Function references
- State updates inside the effect
2. Does it run when it should not?
Check whether the effect is actually necessary.
3. Does it run with stale values?
Check dependencies and closures.
4. Is cleanup working?
Look for:
return () => {// cleanup};
5. Does Strict Mode expose duplicate behavior?
If development causes repeated setup, verify that cleanup correctly reverses setup.
Interview questions & answers
Interview preparation
10 questions grouped by difficulty — expand any card for a model answer and follow-up probe.
Beginner
1What is useEffect?
BeginnerModel answer
useEffectis a React Hook used to synchronize a component with an external system.2What does an empty dependency array mean?
BeginnerModel answer
useEffect(() => { → // ... → }, []);The effect has no reactive dependencies and runs after the component mounts. In development Strict Mode, setup may be intentionally re-run to detect bugs.3Should useEffect be used for derived state?
BeginnerModel answer
Usually no. Prefer:const total = price * quantity;instead of creating another state value and synchronizing it with an effect.4What is the difference between useEffect and event handlers?
BeginnerModel answer
Event handlers respond to user interactions.
Effects synchronize external systems based on rendering and dependency changes.
Intermediate
5When does useEffect run?
IntermediateModel answer
It runs after React commits a render when its dependency conditions indicate that the effect needs to synchronize.
6Why is cleanup needed?
IntermediateModel answer
Cleanup releases resources or removes subscriptions created by the effect. Examples:clearInterval(id);window.removeEventListener("resize", handler);connection.disconnect();7What causes an effect to re-run?
IntermediateModel answer
When one or more dependencies change according to React's dependency comparison.
Advanced
8What does the dependency array do?
AdvancedModel answer
It specifies the reactive values that the effect depends on.useEffect(() => { → // ... → }, [userId]);The effect can re-run whenuserIdchanges.9Can useEffect return a value?
AdvancedModel answer
The effect callback can return a cleanup function:useEffect(() => { → return () => { → // cleanup → }; → }, []);The returned value should be a cleanup function or nothing; it is not meant to return arbitrary data.10Why can an effect run multiple times in development?
AdvancedModel answer
React Strict Mode can intentionally perform an extra setup and cleanup cycle in development to help find effects that are not correctly implemented.
Practice Exercises
Exercise 1: Document Title
Create a counter where:
- The counter uses
useState. - The document title displays the current count.
useEffectsynchronizes the title.
Exercise 2: Window Width
Create a component that:
- Displays browser width.
- Updates when the window is resized.
- Adds the event listener in an effect.
- Removes the event listener during cleanup.
Exercise 3: Timer
Create a timer that:
- Starts an interval.
- Updates a counter every second.
- Cleans up the interval when the component unmounts.
Exercise 4: Local Storage
Create a theme selector that:
- Uses state for the current theme.
- Stores the theme in
localStorage. - Synchronizes storage whenever the theme changes.
Exercise 5: User Fetch
Create a component that:
- Receives
userIdas a prop. - Fetches the corresponding user.
- Displays loading state.
- Displays error state.
- Re-fetches when
userIdchanges.
Exercise 6: Chat Connection
Create a component that:
- Receives
roomId. - Creates a connection when mounted.
- Disconnects during cleanup.
- Reconnects when
roomIdchanges.
Mini Project: Online Status Indicator
Build a reusable component that displays whether the browser is online.
Requirements:
Initial state↓navigator.onLine
Listen for:
onlineoffline
Update state when either event occurs.
Clean up both event listeners.
A possible implementation:
import { useEffect, useState } from "react";function OnlineStatus() {const [isOnline, setIsOnline] = useState(navigator.onLine);useEffect(() => {function handleOnline() {setIsOnline(true);}function handleOffline() {setIsOnline(false);}window.addEventListener("online", handleOnline);window.addEventListener("offline", handleOffline);return () => {window.removeEventListener("online", handleOnline);window.removeEventListener("offline", handleOffline);};}, []);return (<div><strong>{isOnline ? "Online" : "Offline"}</strong></div>);}export default OnlineStatus;
This is a good candidate for extracting into a custom Hook later.
A Practical Decision Tree
When you are about to write an effect, ask:
Do I need to change or synchronize somethingoutside React?│┌────┴────┐│ │No Yes│ │▼ ▼Probably useEffectno effect │needed ▼What values does itdepend on?│▼Add dependencies│▼Does setup createa resource?│┌────┴────┐│ │No Yes│ │▼ ▼Done Return cleanup
This decision process prevents many unnecessary effects.
Quick Cheat Sheet
Import
import { useEffect } from "react";
Basic effect
useEffect(() => {// Synchronization});
No dependencies
useEffect(() => {// Runs after every completed render});
Empty dependencies
useEffect(() => {// No reactive dependencies}, []);
With dependencies
useEffect(() => {// Synchronize with userId}, [userId]);
Cleanup
useEffect(() => {// Setupreturn () => {// Cleanup};}, []);
Event listener
useEffect(() => {window.addEventListener("resize", handleResize);return () => {window.removeEventListener("resize", handleResize);};}, []);
Previous state update
setCount(previous => previous + 1);
Derived data
Prefer:
const total = price * quantity;
instead of:
useEffect(() => {setTotal(price * quantity);}, [price, quantity]);