- React Tutorial
- React Home
- React Setup
- React Introduction
- React ES6
- React Render HTML
- React JSX
- React Components
- React Class
- React Props
- React Events
- React Conditional
- React Lists
- React Forms
- React Router
- React Memo
- React CSS Styling
- React Hooks
- What is a Hook?
- React useState
- React useEffect
- React useContext
- React useRef
- React useReducer
- React useCallback
- React useMemo
- React Custom Hooks
React useEffect Hook
- useEffect is a fundamental React hook that allows you to synchronize a component with an external system. This includes tasks like fetching data, manually changing the DOM, or setting up subscriptions.
- In the past, class components used lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. The
useEffecthook consolidates these into a single, more expressive API.
useEffect as a way to "step outside" of the React rendering flow to interact with the outside world, such as an API or a browser global like window.
Purpose
- External Synchronization: It handles asynchronous operations like API calls or WebSocket connections that shouldn't block the browser from painting the UI.
- Lifecycle Management: It provides a predictable way to manage when code should run based on the component's lifecycle or state changes.
- Resource Management: It ensures that resources (like event listeners or timers) are properly cleaned up to prevent memory leaks in your application.
Syntax
- Import useEffect from the 'react' library.
- The first argument is the effect function containing your logic.
- The second argument is the dependency array, which tells React exactly when to re-run the effect.
import React, { useEffect } from 'react';
useEffect(() => {
// Your side effect logic goes here
return () => {
// Optional: Cleanup code runs before the effect re-runs and on unmount
};
}, [dependencies]); // The effect re-runs whenever any value in this array changes
Runs on every render
If you omit the dependency array entirely, useEffect will execute after every single render of the component. This is rarely what you want, as it can lead to significant performance bottlenecks.
import React, { useEffect, useState } from 'react';
const EveryRenderExample = () => {
const [text, setText] = useState("");
useEffect(() => {
// This runs every time the component updates
console.log('Effect ran because the component re-rendered!');
});
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>Check the console to see the effect firing on every keystroke.</p>
</div>
);
};
export default EveryRenderExample;
Runs on first render
By providing an empty dependency array [], you tell React that this effect does not depend on any values from props or state. Consequently, it only runs once: immediately after the component is first added to the DOM (mounted).
import React, { useEffect, useState } from 'react';
const FirstRenderExample = () => {
const [data, setData] = useState(null);
useEffect(() => {
// Perfect for initial data fetching
fetch('https://api.example.com/items')
.then(res => res.json())
.then(json => setData(json));
console.log('Component mounted. Fetching data...');
}, []); // Empty array = run once on mount
return (
<div>
<p>Data: {data ? JSON.stringify(data) : 'Loading...'}</p>
</div>
);
};
export default FirstRenderExample;
Runs when data changes
When you include variables in the dependency array, React performs a "shallow comparison." If any value in the array has changed since the last render, the effect runs again. This is ideal for responding to user input or prop updates.
import React, { useEffect, useState } from 'react';
const DataChangeExample = ({ userId }) => {
const [user, setUser] = useState(null);
useEffect(() => {
// This effect runs on mount AND whenever userId changes
const fetchUser = async () => {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchUser();
}, [userId]); // Only re-run if userId changes
return (
<div>
<h1>Profile: {user?.name}</h1>
</div>
);
};
export default DataChangeExample;
Cleanup
Some side effects require "cleanup" to avoid memory leaks. Examples include closing a WebSocket, clearing a setTimeout, or removing a global event listener. You perform cleanup by returning a function from your effect.
import React, { useEffect, useState } from 'react';
const WindowWidthTracker = () => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
// Set up the listener
window.addEventListener('resize', handleResize);
// Return the cleanup function
return () => {
// This runs before the component unmounts
window.removeEventListener('resize', handleResize);
};
}, []);
return <p>Window Width: {width}px</p>;
};
Dependency Array
- The dependency array is the "brain" of the
useEffecthook. It controls the execution frequency. - No array: Runs after every render.
- Empty array
[]: Runs only once (on mount). - Array with values
[prop, state]: Runs on mount and wheneverproporstatechanges.
Effect without Cleanup
Many side effects, like logging to the console or one-off API calls, don't require any cleanup. In these cases, you simply don't return anything from the effect function.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Updates the tab title whenever count changes
Summary
useEffect is a versatile tool that bridges the gap between React's declarative UI and the imperative nature of external APIs. By mastering the dependency array and the cleanup function, you can create highly performant and bug-free React applications that interact seamlessly with the real world.