Skip to main content

useEffect as a replacement for lifecycle methods

1. useEffect() merges several lifecycle stages

Class components have separate methods for different stages:

StageMethodWhen it's called
MountingcomponentDidMount()After the first render
UpdatingcomponentDidUpdate()After every update
UnmountingcomponentWillUnmount()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)

javascript
useEffect(() => { console.log('Component mounted'); }, []); // empty array → called only once

Runs once, after the first render (and after being added to the DOM). Used for:

  • loading data,
  • subscriptions,
  • initialization.

Updating (an analog of componentDidUpdate)

javascript
useEffect(() => { console.log('Component updated'); });

Without a dependency array -> called after every render. With a dependency array -> only when a specific value changes:

javascript
useEffect(() => { console.log('count changed'); }, [count]);

Unmounting (an analog of componentWillUnmount)

javascript
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

javascript
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 count changes -> like componentDidUpdate;
  • 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 componentDidMount and componentDidUpdate);
  • 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:

javascript
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:

javascript
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 replace componentDidMount, componentDidUpdate, and componentWillUnmount.

BehaviorClass methoduseEffect
MountingcomponentDidMountuseEffect(..., [])
UpdatingcomponentDidUpdateuseEffect(..., [deps])
UnmountingcomponentWillUnmountreturn cleanup from useEffect

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.