useEffect as a replacement for lifecycle methods
1. useEffect() merges several lifecycle stages
Class components have separate methods for different stages:
| Stage | Method | When it's called |
|---|---|---|
| Mounting | componentDidMount() | After the first render |
| Updating | componentDidUpdate() | After every update |
| Unmounting | componentWillUnmount() | Before removal from the DOM |
But in a functional component, a single useEffect() can play all these roles,
depending on how you specify its dependencies (deps).
2. How useEffect behaves in different modes
Mounting (an analog of componentDidMount)
useEffect(() => {
console.log('Component mounted');
}, []); // empty array → called only onceRuns once, after the first render (and after being added to the DOM). Used for:
- loading data,
- subscriptions,
- initialization.
Updating (an analog of componentDidUpdate)
useEffect(() => {
console.log('Component updated');
});Without a dependency array -> called after every render. With a dependency array -> only when a specific value changes:
useEffect(() => {
console.log('count changed');
}, [count]);Unmounting (an analog of componentWillUnmount)
useEffect(() => {
console.log('Mounted');
return () => {
console.log('Unmounted'); // cleanup
};
}, []);Returning a function from useEffect is the cleanup,
which React calls before the component unmounts.
Used for:
- unsubscribing from events,
- clearing timers,
- closing connections.
3. A single useEffect can play all three roles at once
useEffect(() => {
console.log('Mounting or updating');
const id = setInterval(() => console.log('tick'), 1000);
return () => {
console.log('Cleanup before unmounting or updating');
clearInterval(id);
};
}, [count]);Here one useEffect:
- on the first render -> acts like
componentDidMount; - when
countchanges -> likecomponentDidUpdate; - on removal or before a new run -> like
componentWillUnmount.
4. Why React did it this way
Class components had separate phases and methods, but this:
- forced code duplication (the same API request in
componentDidMountandcomponentDidUpdate); - made it harder to combine logic (e.g. "subscribe + clean up");
- led to sprawling code.
The functional useEffect solves this:
- everything related to one effect's logic lives in one place;
- React itself decides exactly when to call the effect and its cleanup;
- the developer controls the behavior through dependencies.
"Before" and "after" comparison
Class component:
class Example extends React.Component {
componentDidMount() {
console.log('Mounted');
}
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
console.log('Updated');
}
}
componentWillUnmount() {
console.log('Unmounted');
}
render() {
return <div>{this.props.value}</div>;
}
}Functional component with a single useEffect:
function Example({ value }) {
useEffect(() => {
console.log('Mounting or updating');
return () => {
console.log('Unmounting');
};
}, [value]);
return <div>{value}</div>;
}A single useEffect replaces three methods at once, and does it more cleanly and compactly.
5. Summary
useEffect()is a universal side-effect mechanism that, depending on its dependencies (deps), can replacecomponentDidMount,componentDidUpdate, andcomponentWillUnmount.
| Behavior | Class method | useEffect |
|---|---|---|
| Mounting | componentDidMount | useEffect(..., []) |
| Updating | componentDidUpdate | useEffect(..., [deps]) |
| Unmounting | componentWillUnmount | return cleanup from useEffect |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.