Component Lifecycle

Introduction to React Component Lifecycle

React applications are built using components, and these components follow a defined journey from creation to destruction. This journey is called the component lifecycle. Understanding the lifecycle is crucial because it gives developers control over what happens at specific points in a component’s existence. By tapping into lifecycle methods (in class components) or hooks (in functional components), developers can manage side effects, handle data fetching, clean up resources, and optimize rendering.

This post introduces the React component lifecycle, explains its phases, explores class-based lifecycle methods, demonstrates how the lifecycle is managed with hooks in functional components, and covers real-world use cases with examples and best practices.


What is a Component Lifecycle?

The component lifecycle refers to the series of events that happen when a React component is:

  1. Created and inserted into the DOM (Mounting phase).
  2. Updated with new data or props (Updating phase).
  3. Removed from the DOM (Unmounting phase).

Each phase provides opportunities to execute specific code by using lifecycle methods or hooks.


Phases of the Component Lifecycle

React components go through three main phases:

  1. Mounting – The component is created and added to the DOM.
  2. Updating – The component is re-rendered due to changes in props or state.
  3. Unmounting – The component is removed from the DOM.

Lifecycle in Class Components vs Functional Components

  • In class components, React provides built-in lifecycle methods such as componentDidMount, componentDidUpdate, and componentWillUnmount.
  • In functional components, lifecycle is managed using React Hooks like useEffect.

Understanding both is important, though modern React encourages functional components with hooks.


Mounting Phase in Detail

The mounting phase occurs when a component is created and inserted into the DOM.

Class Component Lifecycle Methods During Mounting

  1. constructor()
    • Called before the component mounts.
    • Used to initialize state and bind event handlers.
    class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return <h1>Count: {this.state.count}</h1>; } }
  2. render()
    • Responsible for returning JSX to render the UI.
    • Must be pure (no side effects).
  3. componentDidMount()
    • Invoked immediately after the component is mounted.
    • Ideal for API calls, subscriptions, or DOM manipulations.
    class App extends React.Component { componentDidMount() { console.log("Component mounted!"); } render() { return <h1>Hello World</h1>; } }

Updating Phase in Detail

The updating phase happens when props or state change, causing the component to re-render.

Class Component Lifecycle Methods During Updating

  1. shouldComponentUpdate(nextProps, nextState)
    • Determines whether the component should re-render.
    • Helps optimize performance by preventing unnecessary renders.
    shouldComponentUpdate(nextProps, nextState) { return nextState.count !== this.state.count; }
  2. render()
    • Called again to update the UI with new data.
  3. componentDidUpdate(prevProps, prevState)
    • Invoked after the component updates.
    • Useful for reacting to prop/state changes (e.g., making API calls when props change).
    componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { console.log("Count updated!"); } }

Unmounting Phase in Detail

The unmounting phase occurs when the component is removed from the DOM.

Class Component Lifecycle Method During Unmounting

  1. componentWillUnmount()
    • Invoked just before the component is destroyed.
    • Used to clean up side effects like timers, event listeners, or subscriptions.
    class App extends React.Component { componentWillUnmount() { console.log("Component will unmount!"); } render() { return <h1>Goodbye</h1>; } }

Functional Components and Lifecycle with Hooks

Modern React primarily uses functional components with hooks instead of class-based lifecycle methods.

The useEffect hook is the most important tool for handling lifecycle in functional components.

Example of Mounting with useEffect

import React, { useEffect } from "react";

function App() {
  useEffect(() => {
console.log("Component mounted!");
}, []); return <h1>Hello World</h1>; }

The empty dependency array [] ensures this effect runs only once (on mount).


Example of Updating with useEffect

import React, { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
console.log("Count updated:", count);
}, [count]); return (
&lt;div&gt;
  &lt;p&gt;{count}&lt;/p&gt;
  &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment&lt;/button&gt;
&lt;/div&gt;
); }

Here, the effect runs every time count changes.


Example of Unmounting with useEffect Cleanup

import React, { useEffect } from "react";

function Timer() {
  useEffect(() => {
const timer = setInterval(() =&gt; {
  console.log("Running timer...");
}, 1000);
return () =&gt; {
  clearInterval(timer);
  console.log("Timer cleared on unmount");
};
}, []); return <h1>Timer Running</h1>; }

The function returned from useEffect acts as cleanup and is executed during unmounting.


Lifecycle Diagram Overview

Although we cannot use images here, imagine the following flow:

  • Mounting: constructor → render → componentDidMount
  • Updating: shouldComponentUpdate → render → componentDidUpdate
  • Unmounting: componentWillUnmount

In functional components, all these phases can be managed by carefully using useEffect.


Common Use Cases of Lifecycle Methods and Hooks

1. Fetching Data from APIs

import React, { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
  .then((res) =&gt; res.json())
  .then((data) =&gt; setUsers(data));
}, []); return (
&lt;ul&gt;
  {users.map((user) =&gt; (
    &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;
  ))}
&lt;/ul&gt;
); }

2. Subscribing and Unsubscribing to Events

import React, { useEffect } from "react";

function WindowResize() {
  useEffect(() => {
const handleResize = () =&gt; {
  console.log("Window resized:", window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () =&gt; {
  window.removeEventListener("resize", handleResize);
};
}, []); return <h1>Resize the window to see effect</h1>; }

3. Controlling Performance with shouldComponentUpdate

class ExpensiveComponent extends React.Component {
  shouldComponentUpdate(nextProps) {
return nextProps.value !== this.props.value;
} render() {
console.log("Rendering ExpensiveComponent");
return &lt;h1&gt;{this.props.value}&lt;/h1&gt;;
} }

4. Cleaning Up Timers

function Clock() {
  const [time, setTime] = React.useState(new Date());

  useEffect(() => {
const timer = setInterval(() =&gt; setTime(new Date()), 1000);
return () =&gt; clearInterval(timer);
}, []); return <h1>{time.toLocaleTimeString()}</h1>; }

Best Practices for Working with Lifecycle

  1. Keep Side Effects Inside Lifecycle Methods/Hooks
    • Avoid placing side effects inside render. Use useEffect or componentDidMount instead.
  2. Clean Up Subscriptions and Timers
    • Always return cleanup functions in useEffect to prevent memory leaks.
  3. Minimize Re-Renders
    • Use shouldComponentUpdate (class) or React.memo (functional) to prevent unnecessary renders.
  4. Group Related Logic in Effects
    • Avoid mixing unrelated concerns inside a single useEffect.
  5. Prefer Functional Components with Hooks
    • Modern React applications should prefer functional components for better readability and future-proofing.

Lifecycle Differences Between Class and Functional Components

PhaseClass Component MethodFunctional Component (Hook)
Mountingconstructor, componentDidMountuseEffect with []
UpdatingshouldComponentUpdate, componentDidUpdateuseEffect with [dependencies]
UnmountingcomponentWillUnmountuseEffect cleanup return function

Real-World Example: Chat App with Lifecycle

function ChatRoom() {
  useEffect(() => {
const connect = () =&gt; console.log("Connected to chat server");
const disconnect = () =&gt; console.log("Disconnected from chat server");
connect();
return () =&gt; {
  disconnect();
};
}, []); return <h1>Chat Room</h1>; }

This simulates a real-world scenario where connections must be established on mount and cleaned up on unmount.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *