React.js + Next.js · Module 3. Forms and Effects · Lesson 12 · Point 12
React useEffect Cleanup
React + TypeScript · GoIT Dev Notes
This lesson explains Effect cleanup with useEffect, setInterval, clearInterval, component lifecycle, dependency arrays, StrictMode, React state, timers, and event listeners.
You will learn how React cleans up ongoing external work before an Effect runs again or after a component unmounts, then build a real-time clock that starts and stops safely.
REACT · 01
1. What Is Effect Cleanup?
In React, useEffect() allows us to perform side effects after a component renders.
Examples of side effects include:
- Making HTTP requests.
- Starting timers.
- Setting up intervals.
- Adding event listeners.
- Connecting to external services.
Some Effects need to be stopped when a component is removed or before the Effect runs again.
This process is called Effect cleanup.
React allows us to return a function from useEffect(). This function is called the cleanup function.
Basic Example
File: src/App.tsx
import { useEffect } from "react";
export default function App() {
useEffect(function () {
console.log("Effect started!");
// Cleanup function
return function () {
console.log("Effect cleaned up!");
};
}, []);
return <h1>Hello React!</h1>;
}
Browser Result
When the component mounts, React runs the Effect.
When the component unmounts, React runs the cleanup function.
With StrictMode disabled, the console displays:
Effect started!
When the component unmounts:
Effect cleaned up!
Important: Returning a function does not execute it immediately. React remembers the function and calls it when cleanup is needed.
REACT · 02
2. Understanding setInterval()
JavaScript provides the setInterval() function to execute code repeatedly at a specified time interval.
Example
function showMessage() {
console.log("Hello React!");
}
setInterval(showMessage, 1000);
The console displays:
Hello React!
Hello React!
Hello React!
...
A new message appears approximately every second.
How Does It Work?
setInterval(showMessage, 1000);
setInterval()starts a repeating timer.showMessageis the function to execute.1000represents 1000 milliseconds, or one second.
Common intervals:
| Milliseconds | Time |
|---|---|
| 1000 | 1 second |
| 2000 | 2 seconds |
| 5000 | 5 seconds |
| 60000 | 1 minute |
Important: setInterval() continues running until it is stopped or its execution environment ends.
REACT · 03
3. Stop an Interval with clearInterval()
The setInterval() function returns an identifier.
We can save this identifier in a variable and use clearInterval() to stop the interval.
Example
function showMessage() {
console.log("Hello!");
}
// Start the interval
const intervalId = setInterval(showMessage, 1000);
// Stop the interval
clearInterval(intervalId);
Here:
intervalIdstores the identifier of the interval.clearInterval(intervalId)stops that interval.
In this example, the interval is stopped immediately, before its first scheduled execution.
Remember
setInterval(); // START
clearInterval(); // STOP
When using these functions, we need to pass the appropriate arguments.
REACT · 04
4. Use setInterval Inside useEffect()
We can start an interval when a React component mounts.
File: src/Timer.tsx
import { useEffect } from "react";
export default function Timer() {
useEffect(function () {
setInterval(function () {
console.log("Timer is running!");
}, 1000);
}, []);
return <p>My Timer</p>;
}
Browser Result
What Happens?
- React mounts the component.
useEffect()runs.setInterval()starts.- The interval executes its callback every second.
The empty dependency array:
[]
means the Effect does not rerun because of normal state or prop updates.
What Is the Problem?
If React removes the component, the interval does not automatically stop.
The component disappears, but the interval continues running.
This can waste resources and cause unwanted background work.
REACT · 05
5. Understand Mounting and Unmounting
React components have different stages.
Mounting
Mounting happens when a component is added to the DOM.
Example:
<Timer />
React creates the component and adds it to the page.
Updating
Updating happens when React renders a component again, for example after a state change.
Unmounting
Unmounting happens when React removes a component from the DOM.
For example:
{isOpen && <Timer />}
When isOpen changes from true to false, React removes the Timer component.
Important: Unmounting a component does not automatically stop every external process started by that component.
We must clean up processes such as intervals and event listeners.
REACT · 06
6. Show and Hide a Component
We can use useState() and conditional rendering to control whether a component is visible.
File: src/App.tsx
import { useState } from "react";
import Timer from "./Timer";
export default function App() {
const [isOpen, setIsOpen] = useState(false);
function handleClick() {
setIsOpen(!isOpen);
}
return (
<>
<button onClick={handleClick}>
{isOpen ? "Hide timer" : "Show timer"}
</button>
{isOpen && <Timer />}
</>
);
}
Browser Result
Explanation
const [isOpen, setIsOpen] = useState(false);
Creates a state variable.
falsemeans the Timer is hidden.truemeans the Timer is visible.
setIsOpen(!isOpen);
Changes the Boolean value.
{isOpen && <Timer />}
Renders the Timer only when isOpen is true.
What Happens When We Click?
| Action | React behavior |
|---|---|
| Show timer | Mounts Timer |
| Hide timer | Unmounts Timer |
| Show timer again | Mounts a new Timer |
REACT · 07
7. The Problem: Multiple Intervals Without Cleanup
Consider this incorrect component.
Incorrect code:
import { useEffect } from "react";
export default function Timer() {
useEffect(function () {
setInterval(function () {
console.log("Timer is running!");
}, 1000);
}, []);
return <p>My Timer</p>;
}
Every time the component mounts, a new interval starts.
However, no interval is stopped when the component unmounts.
With StrictMode disabled:
| User action | Active intervals |
|---|---|
| Show timer | 1 |
| Hide timer | 1 |
| Show timer | 2 |
| Hide timer | 2 |
| Show timer | 3 |
After multiple mountings, several intervals run simultaneously.
Instead of one message per second, we may see multiple messages.
This can cause unnecessary CPU usage and resource consumption.
REACT · 08
8. Fix the Problem with Cleanup
We can fix the problem by returning a cleanup function from useEffect().
Correct code — src/Timer.tsx:
import { useEffect } from "react";
export default function Timer() {
useEffect(function () {
// Start the interval
const intervalId = setInterval(function () {
console.log("Timer is running!");
}, 1000);
// Stop the interval during cleanup
return function () {
clearInterval(intervalId);
};
}, []);
return <p>My Timer</p>;
}
Browser Result
Step-by-Step Explanation
Step 1 — Start the interval
const intervalId = setInterval(function () {
console.log("Timer is running!");
}, 1000);
The interval starts and prints a message approximately every second.
Its identifier is stored in intervalId.
Step 2 — Return the cleanup function
return function () {
clearInterval(intervalId);
};
React remembers this function.
Step 3 — Unmount the component
When the component is removed, React executes the cleanup function.
Step 4 — Stop the interval
clearInterval(intervalId);
The interval stops.
Result
With StrictMode disabled:
| User action | Active intervals |
|---|---|
| Show timer | 1 |
| Hide timer | 0 |
| Show timer | 1 |
| Hide timer | 0 |
| Show timer | 1 |
The application no longer accumulates unnecessary intervals.
REACT · 09
9. Cleanup and the Dependency Array
Cleanup does not run only when a component unmounts.
It also runs before an Effect executes again because its dependencies have changed.
Consider this example.
File: src/App.tsx
import { useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
useEffect(function () {
console.log("Effect started:", count);
return function () {
console.log("Cleanup:", count);
};
}, [count]);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Count: {count}
</button>
);
}
Browser Result
Understanding [count]
}, [count]);
The Effect runs:
- After the component mounts.
- Whenever
countchanges.
Before React executes the new Effect, it cleans up the previous one.
Console Output
With StrictMode disabled:
Effect started: 0
After the first click:
Cleanup: 0
Effect started: 1
After the second click:
Cleanup: 1
Effect started: 2
After the third click:
Cleanup: 2
Effect started: 3
Why Does Cleanup Show the Previous Value?
Each execution of the Effect remembers the count value from that particular render.
For example:
return function () {
console.log("Cleanup:", count);
};
If the Effect started when count was 0, its cleanup function remembers 0.
When count changes to 1, React first executes the old cleanup function.
That is why the console displays:
Cleanup: 0
Effect started: 1
Important Rule
The order is:
- Previous Effect cleanup.
- New Effect setup.
React also runs the final cleanup when the component unmounts.
REACT · 10
10. Understand React StrictMode Cleanup Checks
StrictMode helps developers detect potential problems in React components.
In development mode, React performs additional checks.
One important check involves Effects.
Example
import { useEffect } from "react";
export default function Timer() {
useEffect(function () {
console.log("START");
return function () {
console.log("CLEANUP");
};
}, []);
return <p>Timer</p>;
}
Browser Result
When the component is inside a root-level StrictMode, the console normally displays this sequence during initial development mounting:
START
CLEANUP
START
Why Does This Happen?
React tests whether the Effect can safely clean up and start again.
The sequence is:
- Start the Effect.
- Execute its cleanup.
- Start the Effect again.
Without Proper Cleanup
If the Effect starts an interval but does not stop it, the test can leave multiple intervals running.
With Proper Cleanup
useEffect(function () {
const intervalId = setInterval(function () {
console.log("Hello!");
}, 1000);
return function () {
clearInterval(intervalId);
};
}, []);
React can clean up the first interval before starting the next one.
Only the current interval remains active.
Important: These additional StrictMode Effect checks happen in development, not in production. They also depend on where StrictMode is enabled in the component tree.
REACT · 11
11. Work with JavaScript Date
JavaScript provides the Date object for working with dates and times.
Create a Date Object
const time = new Date();
This creates a Date object representing the current date and time.
Display the Time
console.log(time.toLocaleTimeString());
Example output:
14:35:20
The exact format depends on the user's locale and browser settings.
Use Date in React
export default function Timer() {
const time = new Date();
return (
<p>
Current time: {time.toLocaleTimeString()}
</p>
);
}
Browser Result
This displays the current time when the component renders.
However, the time does not update automatically every second.
For automatic updates, we need React state.
REACT · 12
12. Build a Real-Time Clock with React
Now let's combine everything.
We will use:
useState()to store the current time.useEffect()to start the interval.setInterval()to update the time every second.clearInterval()to stop the interval.toLocaleTimeString()to display the time.
Project Structure
src/
├── App.tsx
├── Timer.tsx
└── main.tsx
File: src/Timer.tsx
import { useEffect, useState } from "react";
export default function Timer() {
// Store the current date and time
const [time, setTime] = useState(new Date());
useEffect(function () {
// Update the time every second
const intervalId = setInterval(function () {
setTime(new Date());
}, 1000);
// Stop the interval when the component unmounts
return function () {
clearInterval(intervalId);
};
}, []);
return (
<p>
Current time: {time.toLocaleTimeString()}
</p>
);
}
File: src/App.tsx
import { useState } from "react";
import Timer from "./Timer";
export default function App() {
// Control whether the timer is visible
const [isOpen, setIsOpen] = useState(false);
function handleClick() {
setIsOpen(!isOpen);
}
return (
<div>
<h1>React Timer</h1>
<button onClick={handleClick}>
{isOpen ? "Hide timer" : "Show timer"}
</button>
{isOpen && <Timer />}
</div>
);
}
Browser Result
How Does It Work?
Step 1 — Show the timer
The user clicks:
Show timer
React mounts the Timer component.
Step 2 — Initialize state
const [time, setTime] = useState(new Date());
The initial time is saved in React state.
Step 3 — Start the Effect
useEffect(function () {
// ...
}, []);
React starts the Effect after mounting.
Step 4 — Start the interval
const intervalId = setInterval(function () {
setTime(new Date());
}, 1000);
Every second, a new Date object is created and stored in state.
Step 5 — React Updates the Screen
Calling:
setTime(new Date());
updates React state and triggers a re-render.
The displayed clock updates:
14:35:20
14:35:21
14:35:22
14:35:23
Step 6 — Hide the timer
The user clicks:
Hide timer
React unmounts the component.
Step 7 — Cleanup
return function () {
clearInterval(intervalId);
};
The interval stops.
No unnecessary interval remains active.
REACT · 13
13. Clean Up Event Listeners
Timers are not the only Effects that may need cleanup.
Sometimes we add event listeners to global objects such as window or document.
For example, we may want to detect when the browser window changes size.
Incorrect Example
import { useEffect } from "react";
export default function App() {
useEffect(function () {
function handleResize() {
console.log("Window resized!");
}
window.addEventListener("resize", handleResize);
}, []);
return <h1>Resize the window</h1>;
}
The event listener is added, but it is never removed.
If the component is repeatedly mounted and unmounted, unused listeners may accumulate.
Correct Example
import { useEffect } from "react";
export default function App() {
useEffect(function () {
function handleResize() {
console.log("Window resized!");
}
// Add the event listener
window.addEventListener("resize", handleResize);
// Remove the event listener during cleanup
return function () {
window.removeEventListener("resize", handleResize);
};
}, []);
return <h1>Resize the window</h1>;
}
Browser Result
Explanation
window.addEventListener("resize", handleResize);
Adds a listener that executes handleResize when the browser window is resized.
window.removeEventListener("resize", handleResize);
Removes that listener.
The same function reference must be provided when removing the listener.
Remember
| Start | Cleanup |
|---|---|
setInterval() |
clearInterval() |
setTimeout() |
clearTimeout() |
addEventListener() |
removeEventListener() |
Cleanup is also useful for subscriptions, connections, and other external resources.
Not every Effect requires cleanup. For example, an Effect that only updates the document title may not need one.
REACT · 14
14. Avoid Common Beginner Mistakes
Mistake 1 — Forgetting Cleanup
Incorrect:
useEffect(function () {
setInterval(function () {
console.log("Hello!");
}, 1000);
}, []);
Correct:
useEffect(function () {
const intervalId = setInterval(function () {
console.log("Hello!");
}, 1000);
return function () {
clearInterval(intervalId);
};
}, []);
Mistake 2 — Executing Cleanup Immediately
Incorrect:
useEffect(function () {
const intervalId = setInterval(function () {
console.log("Hello!");
}, 1000);
clearInterval(intervalId);
}, []);
The interval is stopped during the Effect setup itself.
Correct:
useEffect(function () {
const intervalId = setInterval(function () {
console.log("Hello!");
}, 1000);
return function () {
clearInterval(intervalId);
};
}, []);
React receives a function that it can call later.
Mistake 3 — Creating an Interval During Rendering
Incorrect:
export default function Timer() {
setInterval(function () {
console.log("Hello!");
}, 1000);
return <p>Timer</p>;
}
Every render creates another interval.
Correct: Start the interval inside useEffect() and provide cleanup.
Mistake 4 — Thinking Cleanup Runs Immediately
Incorrect assumption:
useEffect(function () {
console.log("START");
return function () {
console.log("STOP");
};
}, []);
It is incorrect to assume that START and STOP always execute immediately one after another.
React stores the cleanup function and calls it when required.
In development StrictMode, React may deliberately run an extra setup-and-cleanup test.
REACT · 15
15. Summary — Key Rules to Remember
useEffect()is used to synchronize a React component with external systems and other side effects.setInterval()executes a function repeatedly.clearInterval()stops an interval.- Always store an interval identifier if you need to stop that interval.
- An interval does not automatically stop when its React component unmounts.
- Return a cleanup function from
useEffect()to release resources. - Cleanup runs when the component unmounts.
- Cleanup also runs before an Effect executes again because dependencies changed.
- With
[count], React cleans up the previous Effect before starting the new Effect. - React
StrictModeperforms extra development checks to help detect missing cleanup. useState()can store the current time and trigger clock updates.new Date()creates aDateobject representing the current time.toLocaleTimeString()formats the time as a readable string.- Global event listeners should be removed when they are no longer needed.
- Not every Effect needs cleanup, but Effects that start ongoing external work often do.
The Most Important Pattern
useEffect(function () {
// 1. SETUP
// Start something
return function () {
// 2. CLEANUP
// Stop or remove what was started
};
}, []);
The golden rule: If your Effect starts something that continues running, make sure you understand when and how to stop it.