In older React applications (pre-hooks), class components relied on special "lifecycle methods" to handle tasks that happen at different stages of a component’s existence. These methods include:
Starting with React 16.8, Hooks introduced useEffect, a function-based approach to mimic these lifecycle behaviors in function components.
By understanding how lifecycle methods map to useEffect, you can convert old class components into the more modern function-based approach.
Let's consider a class component that:
count: 0count to 5 after 5 seconds (componentDidMount)count changes (componentDidUpdate)componentWillUnmount)Folder And Filenames: Suppose you have a src/components folder with class_lifecycle_example.js. It might look like this:
import React from 'react';
class ClassLifecycleExample extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
setTimeout(() => {
this.setState({ count: 5 });
}, 5000);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('hello world!');
}
}
componentWillUnmount() {
console.log('cleanup');
}
render() {
const { count } = this.state;
return (
<>
<div>{count}</div>
<button onClick={() =>
this.setState(state => ({ count: state.count + 1 }))
}>
Increment
</button>
</>
);
}
}
export default ClassLifecycleExample;
You now want to refactor it to a function-based component that uses useEffect.
Refactoring Steps / Follow Along:
class ClassLifecycleExample extends React.Component,
you define function LifecycleFunctionExample().constructor and this.state usage. Instead, use useState to manage count.useEffect with an empty dependency array ([]), so it only runs once, after the first render.useEffect that depends on count. Inside, compare or just assume that if it runs, count changed.
If you specifically need to compare old vs. new values, you can store them in a ref or use the second argument approach in a more advanced manner.
But typically, if count changes, that effect will run.useEffect has a return statement that will be invoked when your component unmounts.
That’s where you put "cleanup" code.Converted File: src/components/lifecycle_function_example.js
import React, { useState, useEffect } from 'react';
function LifecycleFunctionExample() {
const [count, setCount] = useState(0);
// Imitates componentDidMount
useEffect(() => {
// After 5 seconds, set count to 5
const timerId = setTimeout(() => {
setCount(5);
}, 5000);
// Imitates componentWillUnmount
return () => {
clearTimeout(timerId);
console.log('cleanup');
};
}, []);
// Imitates componentDidUpdate for count
useEffect(() => {
console.log('hello world!');
}, [count]);
return (
<>
<div>{count}</div>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment
</button>
</>
);
}
export default LifecycleFunctionExample;
Notice how we used two separate useEffect calls:
one for the initial mount/unmount logic, and one for reacting to changes in count.
This separation keeps your code organized, letting each useEffect handle a single responsibility.
You might think of the component's lifecycle like a person's life events:
The useEffect hook is a scheduling system that neatly lines up these events
in your function-based approach, ensuring you can add or remove effects as needed.
This pattern commonly appears when you need to:
useEffect.In the example above:
[],
meaning it only runs once after the first render, imitating componentDidMount.
The return statement in that same effect is the componentWillUnmount logic
(running only when the component is about to disappear).
[count].
That means each time count changes, the effect runs, similar to componentDidUpdate
for changes in count.
If you had multiple state variables, you could watch them individually by passing those state variables
into the dependency array.
With class components, you might have multiple pieces of logic jammed into componentDidMount
or componentDidUpdate. With function components, you can separate them into multiple useEffect calls,
making the code more maintainable and concise.
You might also consider:
useEffect and useState across multiple components, extract that logic into a custom hook.componentDidUpdate if you used setState incorrectly.In this reading, you discovered how to:
componentDidMount, componentDidUpdate, componentWillUnmount)useEffect hooks in a function componentcomponentDidMount, a cleanup function to simulate componentWillUnmount, and a dependency array to replicate the conditional logic of componentDidUpdate
By mastering this mapping of lifecycle methods to useEffect, you can confidently migrate older class component code to modern function-based code, leading to more consistent and streamlined React applications.