Skip to content

React Lifecycle Using Hooks

In React functional components, traditional class lifecycle methods are replaced primarily by the useEffect hook.

Class Component Lifecycle Hooks Equivalent
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {}, [dependency])
componentWillUnmount useEffect(() => { return () => {} }, [])
flowchart TD
    A[Component Created] --> B[Render]
    B --> C["Mount Effect<br/>useEffect(..., [])"]
    C --> D[User Interaction / State Change]
    D --> E[Render Again]
    E --> F["Update Effect<br/>useEffect(..., [deps])"]
    F --> D
    D --> G[Component Removed]
    G --> H[Cleanup Function Executes]

Runs only once when the component is first rendered.

import { useEffect } from "react";
function User() {
useEffect(() => {
console.log("Component Mounted");
}, []);
return <h1>User Component</h1>;
}
Component Mounted

The empty dependency array ([]) ensures the effect runs only once after the initial render.


Runs whenever one of the specified dependencies changes.

import { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count Updated:", count);
}, [count]);
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}
Initial Render
->
Effect Runs
Count Changes
->
Effect Runs Again

The cleanup function executes before the component is removed.

import { useEffect } from "react";
function Timer() {
useEffect(() => {
const id = setInterval(() => {
console.log("Running...");
}, 1000);
return () => {
clearInterval(id);
console.log("Component Unmounted");
};
}, []);
return <h1>Timer</h1>;
}
  • Clear timers
  • Remove event listeners
  • Close WebSocket connections
  • Cancel API requests

import { useState, useEffect } from "react";
function Demo() {
const [count, setCount] = useState(0);
console.log("Render");
useEffect(() => {
console.log("Mounted");
return () => {
console.log("Unmounted");
};
}, []);
useEffect(() => {
console.log("Count Changed:", count);
}, [count]);
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}
sequenceDiagram
    participant R as React
    participant C as Component

    R->>C: Initial Render
    C-->>R: Render
    R->>C: Mount Effect
    C-->>R: Mounted
    R->>C: Dependency Effect
    C-->>R: Count Changed: 0

    R->>C: State Update
    C-->>R: Render
    R->>C: Dependency Effect
    C-->>R: Count Changed: 1

    R->>C: Unmount
    C-->>R: Cleanup (Unmounted)

useEffect’s dependency array determines whether it runs after every render, only once on mount, or when specific values change. See the React useEffect Interview Guide for the no-array/empty-array/multi-dependency breakdown and why omitting a dependency causes stale values.


Why use useEffect instead of lifecycle methods?

Section titled “Why use useEffect instead of lifecycle methods?”
  • Keeps related logic together.
  • Eliminates duplication between componentDidMount and componentDidUpdate.
  • Improves readability and maintainability.

Can useEffect replace all lifecycle methods?

Section titled “Can useEffect replace all lifecycle methods?”

Almost. It covers mount, update, and unmount behavior. However, when DOM measurements or synchronous work before the browser paints are required, use useLayoutEffect.

useLayoutEffect(() => {
// Runs before browser paint
});
Mount -> useEffect(..., [])
Update -> useEffect(..., [deps])
Unmount -> return cleanup function
All renders -> useEffect(...)
  • useEffect is the primary Hook for implementing lifecycle behavior in functional components.
  • The dependency array determines when an effect runs.
  • Cleanup functions prevent memory leaks by releasing resources during unmount.
  • useLayoutEffect is used for synchronous DOM-related work before painting.
  • Understanding lifecycle behavior with Hooks is a common React interview topic.