useEffect can impact your app’s performance, so it's important to use it wisely. Let's break down why and clarify some key concepts in React.js.
Initial Render vs Re-render
- Initial render: This happens the first time a component is rendered to the DOM, typically when the app loads or the root component is mounted.
- Re-render: This occurs when the component’s state or props change, prompting React to update the DOM accordingly. It uses a virtual DOM to optimize updates and apply only the necessary changes.
Example Code:
import { useState, useEffect } from 'react';
const App = () => {
const [toggle, setToggle] = useState(false);
return (
<>
<h2>useEffect cleanup function</h2>
<button className="btn" onClick={() => setToggle(!toggle)}>
Toggle component
</button>
{toggle && <AnotherComponent />}
</>
);
};
const AnotherComponent = () => {
useEffect(() => {
console.log('mounting - another component');
}, []);
return <h2>Another Component</h2>;
};
export default App;
Clicking the button toggles AnotherComponent, triggering multiple mounts (initial renders).
Performance Concerns
This setup can cause performance issues, especially with things like:
- Timers (setInterval): Equivalent to subscribing to an event.
- Event listeners: Another source of potential performance problems.
These issues arise because the side effects (e.g., timers, event listeners) aren't properly cleaned up, leading to memory leaks or unnecessary work.
Solution: useEffect Cleanup Function
To address these issues, use the useEffect cleanup function. This ensures that any side effects are properly reversed when the component unmounts or when dependencies change.
Simple Example:
useEffect(() => {
// Effect code, like adding a timer or event listener.
return () => {
// Cleanup code, like clearing the timer or removing the event listener.
};
}, []);
This guarantees that side effects are cleaned up when the component unmounts, improving performance.
Also, when using useEffect, you might want to check out this helpful article: You might not need an effect. It can help you decide if useEffect is truly necessary in certain scenarios.
Fetching Data with Libraries (React Query, RTK Query, SWR, Next.js)
Instead of manually handling data fetching in useEffect, libraries like React Query, RTK Query, SWR, or Next.js can simplify this process. These libraries manage data fetching, caching, and state updates efficiently.
Here’s how it looks using one of these libraries:
import { useHook } from 'library';
function Example() {
const { data, error, isLoading } = useHook('url', fetcher);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Hello, {data.name}!</div>;
}
Benefits:
-
Simplified Code: No need for
useEffector manual state management for loading, error, and data states. - Automatic Caching: These libraries handle caching, retries, and pagination out of the box.
- Improved Performance: Data fetching logic is optimized and separated from the UI layer.
If you're building a simple app, useEffect might still be enough. But be mindful of alternatives when your app grows in complexity.
By using libraries like React Query, RTK Query, SWR, or Next.js, data fetching becomes cleaner and more efficient, reducing boilerplate code and improving the user experience.
Credits: John Smilga’s React course.

Top comments (3)
setIntervalExample:Problem:
When you click the toggle button multiple times, you'll notice that the interval is set many times, causing performance issues.
Solution:
The solution is to use the cleanup function within
useEffect. The cleanup function will run when the component unmounts, clearing the interval and preventing multiple intervals from stacking.The relevant code is commented out in the example. The key point is to use
clearInterval(intervalId)to clean up the interval.Screenshot for context:
Cleanup Function in
useEffectThe cleanup function isn't always necessary, but it’s useful in cases where side effects need to be reversed, like clearing intervals or event listeners. Here’s an example to demonstrate when the cleanup function is called:
Explanation:
In this example, every time the
countstate changes (which happens when you click the header), the cleanup function is called before the effect is re-run.So, when you click the header:
This ensures that side effects are properly cleaned up before a new effect is applied.
Event Listener Example:
Problem:
When you toggle the component multiple times and click on 'Refresh event listeners', you may notice that the event listener gets attached multiple times. This is because every time the component mounts, the event listener is added again without removing the previous one.
Solution:
You can solve this by uncommenting the cleanup code inside the
useEffect. The cleanup function removes the event listener when the component unmounts or before the next effect runs, ensuring that only one event listener is attached.This keeps your app from attaching multiple event listeners.