React lifecycle methods are special methods that get called at specific points during the life of a component. They provide hooks for various stages such as mounting, updating, and unmounting.
In class components, you handle lifecycle events using lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
. However, with the introduction of hooks, functional components can handle lifecycle events using the useEffect
hook.
In this module, we will cover:
- React lifecycle methods in class components
- The
useEffect
hook and how it replaces lifecycle methods in functional components - How to use
useEffect
for fetching data, subscribing to events, and cleaning up resources - Best practices for using
useEffect
effectively
React Lifecycle Methods in Class Components
Before hooks were introduced, class components used lifecycle methods to perform actions at various points in the component’s life. These methods are called automatically at specific times during a component’s existence.
Key Lifecycle Methods in Class Components:
componentDidMount
: This method is called once, immediately after the component is mounted (inserted into the tree).componentDidUpdate
: This method is called after the component updates, i.e., when the state or props change.componentWillUnmount
: This method is called just before the component is unmounted and destroyed. It is typically used to clean up resources like timers or event listeners.
Example of Lifecycle Methods in a Class Component:
jsxCopyEditimport React, { Component } from 'react';
class UserList extends Component {
constructor(props) {
super(props);
this.state = { users: [] };
}
// Fetch data when the component mounts
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => this.setState({ users: data }));
}
// Clean up before unmounting
componentWillUnmount() {
console.log('UserList component is being removed');
}
render() {
return (
<div>
<h1>Users</h1>
<ul>
{this.state.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
}
export default UserList;
In this example:
componentDidMount
fetches the list of users when the component is first rendered.componentWillUnmount
could be used for cleanup actions (e.g., removing event listeners or cancelling API calls).
The useEffect
Hook in Functional Components
The useEffect
hook is used in functional components to perform side effects, such as fetching data, setting up subscriptions, or cleaning up resources. The useEffect
hook can replace most lifecycle methods from class components.
Basic Syntax of useEffect
:
jsxCopyEdituseEffect(() => {
// Code to run when the component mounts or updates
return () => {
// Cleanup code, runs when the component unmounts or before the effect is re-run
};
}, [dependencies]);
- The effect callback (first argument) runs after every render by default.
- The cleanup function (optional return from the effect) runs before the component unmounts or before the effect is re-run.
- The dependency array (second argument) allows you to control when the effect runs, by specifying which state or props to watch.
Using useEffect
for Side Effects
Example 1: Fetching Data with useEffect
In class components, we use componentDidMount
to fetch data. In functional components, we can achieve the same using useEffect
.
jsxCopyEditimport React, { useState, useEffect } from 'react';
const UserList = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => setUsers(data));
}, []); // Empty dependency array means this effect runs only once (like componentDidMount)
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
export default UserList;
- The effect runs once when the component mounts because the dependency array is empty (
[]
). - If you want the effect to run when a specific state or prop changes, you can include that state or prop in the dependency array.
Example 2: Subscribing to an Event with useEffect
You can also use useEffect
to set up event listeners or subscriptions. For example, you can listen for window resize events and update the state accordingly.
jsxCopyEditimport React, { useState, useEffect } from 'react';
const WindowSize = () => {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// Cleanup the event listener when the component unmounts
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty dependency array means this effect runs only once when the component mounts
return (
<div>
<h1>Window Width: {windowWidth}</h1>
</div>
);
};
export default WindowSize;
- The event listener is added when the component mounts, and it is cleaned up when the component unmounts by returning a cleanup function from the
useEffect
hook.
Using useEffect
with Dependencies
The useEffect
hook allows you to specify dependencies that determine when the effect should run. If a state or prop inside the dependency array changes, React will re-run the effect.
Example: Updating Based on State Changes
jsxCopyEditimport React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// Effect that runs whenever `count` changes
document.title = `Count: ${count}`;
}, [count]); // The effect runs only when `count` changes
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default Counter;
- The effect updates the
document.title
to reflect the current value ofcount
. - The effect is triggered only when the
count
value changes, thanks to the dependency array[count]
.
Cleanup with useEffect
If an effect involves resources that need to be cleaned up (such as event listeners, timers, or subscriptions), you can return a cleanup function inside useEffect
. This function will run before the effect re-runs or when the component unmounts.
Example of Cleanup (Timer):
jsxCopyEditimport React, { useState, useEffect } from 'react';
const Timer = () => {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
// Cleanup function to clear the interval when the component unmounts
return () => clearInterval(intervalId);
}, []); // Empty dependency array means this effect runs only once when the component mounts
return (
<div>
<h1>{seconds} seconds</h1>
</div>
);
};
export default Timer;
- The timer is started when the component mounts and cleaned up when the component unmounts, preventing any memory leaks.
Best Practices for Using useEffect
- Specify Dependencies: Always include dependencies in the dependency array to control when the effect should run. If an effect doesn’t have any dependencies, use an empty array
[]
to make it run only once on mount and unmount. - Cleanup: Use the cleanup function to clean up resources such as timers, event listeners, and subscriptions.
- Multiple Effects: You can use multiple
useEffect
hooks in the same component to handle different side effects independently. - Avoid Complex Logic Inside
useEffect
: Keep the logic insideuseEffect
simple and clean. Complex logic should be moved outside ofuseEffect
for readability and maintainability.
Summary
In this module, we covered:
- React lifecycle methods in class components (
componentDidMount
,componentDidUpdate
,componentWillUnmount
) - How to handle lifecycle events using the
useEffect
hook in functional components - Performing side effects such as fetching data, subscribing to events, and cleaning up resources with
useEffect
- Best practices for using
useEffect
effectively and avoiding common pitfalls
The useEffect
hook is essential for handling side effects in React functional components, and mastering it will make your applications more powerful and efficient.