Cleanup Functions in React JS
Cleanup Functions are an important concept in React JS that helps developers stop, remove, disconnect, cancel, or undo work started by the useEffect() Hook. They help manage resources such as timers, event listeners, API requests, subscriptions, and WebSocket connections when those resources are no longer needed.
Cleanup functions improve application reliability by preventing duplicate listeners, unnecessary background operations, outdated asynchronous updates, and resource leaks.
Course Link: JustAcademy React JS Training Course
Demo Link: Register for React JS Course Demo
1. What Is a Cleanup Function?
A cleanup function is an optional function returned by the setup function inside useEffect(). React executes it when the previous Effect needs to be stopped before a new setup, or when the component is removed from the React tree.
For example, if an Effect starts a timer, the cleanup function can clear the timer. If an Effect subscribes to a service, cleanup can unsubscribe from it.
Syntax
import { useEffect } from 'react';
function Example() {
useEffect(() => {
// Setup logic
console.log("Effect started");
return () => {
// Cleanup logic
console.log("Effect cleaned up");
};
}, []);
return <h1>Cleanup Function Example</h1>;
}
export default Example;
Explanation:
useEffect() declares an Effect.
- The setup function performs the required operation.
- The returned cleanup function stops or reverses that operation.
- The dependency array determines when the Effect must synchronize again.
2. Why Are Cleanup Functions Important?
Cleanup functions are important because some operations continue running independently of React's rendering process. If those operations are not stopped when necessary, they may cause unexpected behavior.
- Prevent resource leaks: Release resources that are no longer required.
- Clear timers: Stop intervals and scheduled timeouts.
- Remove event listeners: Prevent old event handlers from continuing to run.
- Unsubscribe from services: Stop receiving unnecessary external updates.
- Close connections: Disconnect WebSockets and other external connections.
- Handle asynchronous work: Abort supported requests or ignore outdated responses.
- Avoid duplicate operations: Ensure that repeated Effect setup does not leave multiple resources active.
3. When Does a Cleanup Function Execute?
3.1 Before an Effect Runs Again
When one or more dependencies change, React runs the previous cleanup function before running the new Effect setup function.
3.2 When a Component Unmounts
When a component is removed from the React tree, React runs the cleanup functions for its active Effects.
3.3 During Strict Mode Development Checks
When React Strict Mode is enabled, React performs an additional setup-and-cleanup cycle for Effects during initial mounting in development. This helps detect missing or incorrect cleanup logic.
Effect Execution Flow
Component renders
|
v
React commits the UI
|
v
Effect setup executes
|
v
Dependencies change?
|
+---- Yes ----> Previous cleanup executes
| |
| v
| New setup executes
|
+---- No -----> Effect remains active
Component unmounts
|
v
Final cleanup executes
Important: React does not require every Effect to return a cleanup function. Cleanup is needed when the Effect starts work that must be stopped or undone.
4. Cleanup Function with setInterval()
The setInterval() function executes a callback repeatedly after a specified interval. Use clearInterval() to stop it when it is no longer needed.
Example: Timer
import { useEffect, useState } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds(previous => previous + 1);
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return <h2>Seconds: {seconds}</h2>;
}
export default Timer;
Explanation:
- The component renders and React commits the UI.
- The Effect starts an interval that increments the counter every second.
- The cleanup function calls
clearInterval(intervalId).
- When the component unmounts, the interval is cleared.
The functional state updater previous => previous + 1 uses the latest state without requiring seconds in the dependency array. This avoids recreating the interval whenever the count changes.
5. Cleanup Function with setTimeout()
The setTimeout() function schedules a callback to execute after a specified delay. The scheduled callback can be cancelled using clearTimeout().
Example: Automatically Hide a Message
import { useEffect, useState } from 'react';
function Message() {
const [visible, setVisible] = useState(true);
useEffect(() => {
const timeoutId = setTimeout(() => {
setVisible(false);
}, 5000);
return () => {
clearTimeout(timeoutId);
};
}, []);
return (
<div>
{visible && <p>This message disappears after five seconds.</p>}
</div>
);
}
export default Message;
Explanation: The Effect schedules a timeout. If the component unmounts before the timeout fires, cleanup cancels it. Clearing a timeout after its callback has already executed does not undo that callback.
6. Cleanup Function with Event Listeners
Browser event listeners may continue responding to events even when a component no longer needs them. Cleanup removes listeners that were registered by an Effect.
Example: Window Resize Event
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 <h2>Window Width: {width}px</h2>;
}
export default WindowSize;
Explanation:
- The Effect registers a resize listener.
- When the browser window is resized, the handler updates the width.
- The cleanup function removes the same event handler.
Note: This example assumes a browser environment. In server-rendered applications, access to window should be handled safely on the client.
7. Cleanup Function with API Requests
An API request may finish after a component has updated or unmounted. Cleanup can abort supported requests or prevent outdated responses from updating the UI.
Example: Abort an API Request
import { useEffect, useState } from 'react';
function UserDetails({ userId }) {
const [user, setUser] = useState(null);
const [error, setError] = useState("");
useEffect(() => {
const controller = new AbortController();
async function loadUser() {
try {
setUser(null);
setError("");
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`,
{ signal: controller.signal }
);
if (!response.ok) {
throw new Error("Failed to load user");
}
const data = await response.json();
setUser(data);
} catch (err) {
if (err.name !== "AbortError") {
setError(err.message);
}
}
}
loadUser();
return () => {
controller.abort();
};
}, [userId]);
if (error) return <p>Error: {error}</p>;
if (!user) return <p>Loading user...</p>;
return <h2>{user.name}</h2>;
}
export default UserDetails;
Explanation:
- The Effect starts a request for the current
userId.
- If
userId changes or the component unmounts, cleanup calls controller.abort().
- The aborted request is handled separately from other errors.
- The next Effect setup fetches the new user's details when required.
This example uses the Fetch API, which supports AbortController. For asynchronous operations that cannot be cancelled, a cleanup flag can be used to ignore stale results.
8. Cleanup Function with Subscriptions
Subscriptions allow a component to receive updates from an external service. Cleanup should unsubscribe when the component no longer needs those updates.
Example
import { useEffect } from 'react';
function Notifications({ notificationService }) {
useEffect(() => {
function handleNotification(notification) {
console.log("New notification:", notification);
}
notificationService.subscribe(handleNotification);
return () => {
notificationService.unsubscribe(handleNotification);
};
}, [notificationService]);
return <p>Listening for notifications...</p>;
}
export default Notifications;
Explanation: The Effect subscribes to the notification service. If the component unmounts or the service dependency changes, cleanup removes the same handler from the subscription.
Note: This example assumes the service provides matching subscribe() and unsubscribe() methods.
9. Cleanup Function with WebSocket Connections
A WebSocket maintains a persistent connection between a client and a server. Cleanup should remove registered event listeners and close the connection when it is no longer required.
Example
import { useEffect, useState } from 'react';
function LiveUpdates({ url }) {
const [message, setMessage] = useState("");
useEffect(() => {
const socket = new WebSocket(url);
function handleMessage(event) {
setMessage(event.data);
}
socket.addEventListener("message", handleMessage);
return () => {
socket.removeEventListener("message", handleMessage);
socket.close();
};
}, [url]);
return <p>Latest message: {message}</p>;
}
export default LiveUpdates;
Explanation: The Effect opens a connection and listens for messages. If the URL changes or the component unmounts, cleanup removes the listener and closes the connection.
10. Cleanup Functions and Dependency Changes
Cleanup does not run only when a component unmounts. It also runs before an Effect is set up again because a dependency has changed.
Example: Switching Chat Rooms
import { useEffect } from 'react';
function ChatRoom({ roomId }) {
useEffect(() => {
console.log("Connecting to:", roomId);
return () => {
console.log("Disconnecting from:", roomId);
};
}, [roomId]);
return <h2>Room: {roomId}</h2>;
}
export default ChatRoom;
If roomId changes from "general" to "music", the normal sequence is:
- React commits the UI update.
- The previous cleanup runs using the old
roomId, which is "general".
- The new Effect setup runs using the new
roomId, which is "music".
When the component unmounts, React runs the cleanup for the active Effect.
11. Cleanup Functions in React Strict Mode
React Strict Mode is a development tool that helps identify bugs in components and Effects. When enabled, React performs an extra setup-and-cleanup cycle for Effects during initial mounting in development.
Example
useEffect(() => {
console.log("Setup");
return () => {
console.log("Cleanup");
};
}, []);
You may see the following sequence in development with Strict Mode enabled:
Setup
Cleanup
Setup
This is intentional. It checks whether cleanup correctly reverses the setup. Do not try to hide the extra execution with a ref; make sure every setup has the appropriate cleanup instead.
12. Common Cleanup Function Mistakes
| Mistake |
Problem |
Correct Approach |
| Not clearing an interval |
The timer may continue running when it is no longer needed. |
Call clearInterval() in cleanup. |
| Not removing event listeners |
Old handlers may continue responding to events. |
Remove the same handler using removeEventListener(). |
| Not unsubscribing |
The component may keep receiving external updates. |
Call the subscription API's matching unsubscribe method. |
| Not handling outdated requests |
An older response may overwrite newer data. |
Abort supported requests or ignore stale results. |
| Cleanup does not match setup |
Resources may remain active or be released incorrectly. |
Make cleanup reverse the work started by setup. |
| Assuming cleanup runs only on unmount |
Dependency changes can also trigger cleanup. |
Account for cleanup before Effect re-execution. |
| Trying to prevent Strict Mode checks |
Missing cleanup problems may remain hidden. |
Make setup and cleanup safe to repeat. |
13. Best Practices for Cleanup Functions
- Return a cleanup function when an Effect starts work that must be stopped or undone.
- Keep setup and cleanup logic together inside the same Effect.
- Use
clearTimeout() and clearInterval() for scheduled operations.
- Remove event listeners using the same event type and handler that were registered.
- Unsubscribe from external services when subscriptions are no longer needed.
- Abort supported requests or ignore outdated asynchronous results.
- Include all reactive dependencies used by the Effect.
- Do not add cleanup code for resources that do not require manual cleanup.
- Test Effects with Strict Mode enabled during development.
- Avoid using Effects for calculations that can be performed directly during rendering.
14. Difference Between Setup and Cleanup Functions
| Feature |
Setup Function |
Cleanup Function |
| Purpose |
Starts or establishes work. |
Stops, removes, or reverses that work. |
| Execution |
Runs when the Effect is set up. |
Runs before necessary re-setup and on unmount. |
| Timer example |
Creates an interval. |
Clears the interval. |
| Event example |
Adds an event listener. |
Removes the event listener. |
| Connection example |
Connects to a server. |
Disconnects from the server. |
| Return value |
May return a cleanup function. |
Usually does not return another cleanup function. |
15. Interview Questions and Answers
Q1. What is a cleanup function in React?
Answer: It is an optional function returned by a useEffect() setup function to stop, remove, or undo work started by the Effect.
Q2. When does a cleanup function execute?
Answer: It runs before an Effect re-executes with changed dependencies and when the component unmounts. Strict Mode can also trigger an extra cleanup cycle in development.
Q3. Why are cleanup functions important?
Answer: They help prevent unnecessary timers, listeners, subscriptions, and connections from remaining active.
Q4. How do you clean up a setInterval timer?
Answer: Store the interval ID and call clearInterval(intervalId) in the cleanup function.
Q5. How do you remove an event listener?
Answer: Call removeEventListener() with the same event type and handler used to register the listener.
Q6. Is a cleanup function always required?
Answer: No. It is required only when the Effect starts work that must be stopped or undone.
Q7. Does cleanup run before the next Effect setup?
Answer: Yes. When dependencies change, React runs the previous cleanup before the new setup.
Q8. Why might cleanup run during initial mounting in development?
Answer: React Strict Mode performs an extra setup-and-cleanup cycle to check whether the Effect is implemented correctly.
Q9. How can cleanup help with API requests?
Answer: It can abort supported requests using AbortController or prevent outdated responses from updating the UI.
Q10. What is the main rule for cleanup logic?
Answer: Cleanup should mirror setup and stop or reverse the work that setup started.
16. Practice Exercises
- Create a timer that increments a counter every second and clears the interval on unmount.
- Add a window resize listener and remove it in cleanup.
- Create a component that fetches user data and aborts the request when the user ID changes.
- Build a subscription example with matching subscribe and unsubscribe methods.
- Create a WebSocket example that closes the connection when the component unmounts.
- Enable React Strict Mode and verify that cleanup correctly reverses the setup.
17. Quick Revision Checklist
- Understand the purpose of cleanup functions.
- Know how to return cleanup from
useEffect().
- Understand cleanup on dependency changes and component unmounting.
- Practice clearing timers and removing event listeners.
- Know how to clean up subscriptions and external connections.
- Understand request cancellation and stale-response handling.
- Recognize the extra development setup-and-cleanup cycle in React Strict Mode.
18. Conclusion
Cleanup functions are essential for managing resources started by React Effects. They help stop timers, remove event listeners, unsubscribe from services, close connections, and handle asynchronous work when it is no longer relevant.
The key principle is simple: when an Effect starts work that must be stopped or undone, provide the appropriate cleanup. Correct cleanup makes React applications more predictable, maintainable, and reliable.
Official React Documentation: useEffect Reference | Synchronizing with Effects | Lifecycle of Reactive Effects
Learn React JS with JustAcademy: Explore React JS Training | Register for a Course Demo