Skip to main content

Class Components Overview

While functional components with hooks are now the standard, understanding class components is valuable for maintaining legacy code and understanding React’s evolution.
New projects should use functional components with hooks. Class components are primarily for legacy codebases.

Creating a Class Component

Class components extend React.Component and must have a render() method:
import { Component } from 'react';

class Users extends Component {
  render() {
    return (
      <ul>
        {this.props.users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

export default Users;

Working with State

State is initialized in the constructor and updated with setState():
import { Component } from 'react';

class UserFinder extends Component {
  constructor() {
    super();
    this.state = {
      filteredUsers: [],
      searchTerm: '',
    };
  }

  searchChangeHandler(event) {
    this.setState({ searchTerm: event.target.value });
  }

  render() {
    return (
      <div>
        <input
          type="search"
          onChange={this.searchChangeHandler.bind(this)}
        />
        <Users users={this.state.filteredUsers} />
      </div>
    );
  }
}
Use .bind(this) or arrow functions to ensure methods have access to the component instance.

Lifecycle Methods

Class components have lifecycle methods for different phases of the component’s existence:
import { Component } from 'react';

class UserFinder extends Component {
  constructor() {
    super();
    this.state = {
      filteredUsers: [],
      searchTerm: '',
    };
  }

  componentDidMount() {
    // Runs after component is mounted (like useEffect with [])
    // Perfect for HTTP requests, subscriptions
    this.setState({ filteredUsers: DUMMY_USERS });
  }

  componentDidUpdate(prevProps, prevState) {
    // Runs after component updates (like useEffect with dependencies)
    if (prevState.searchTerm !== this.state.searchTerm) {
      this.setState({
        filteredUsers: DUMMY_USERS.filter((user) =>
          user.name.includes(this.state.searchTerm)
        ),
      });
    }
  }

  componentWillUnmount() {
    // Runs before component is removed (cleanup)
    // Perfect for clearing timers, canceling requests
  }

  searchChangeHandler(event) {
    this.setState({ searchTerm: event.target.value });
  }

  render() {
    return (
      <div className="finder">
        <input type="search" onChange={this.searchChangeHandler.bind(this)} />
        <Users users={this.state.filteredUsers} />
      </div>
    );
  }
}

Lifecycle Methods Explained

1

componentDidMount()

Called once after the component is rendered for the first time. Use for:
  • Fetching initial data
  • Setting up subscriptions
  • Starting timers
Functional equivalent: useEffect(() => { ... }, [])
2

componentDidUpdate(prevProps, prevState)

Called after every update (except the initial render). Use for:
  • Responding to prop or state changes
  • Fetching data based on changes
  • Updating DOM
Functional equivalent: useEffect(() => { ... }, [dependencies])
3

componentWillUnmount()

Called right before the component is removed from the DOM. Use for:
  • Cleanup subscriptions
  • Clearing timers
  • Canceling network requests
Functional equivalent: Cleanup function in useEffect

Error Boundaries

Error boundaries are a feature only available in class components (as of now):
import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor() {
    super();
    this.state = { hasError: false };
  }

  componentDidCatch(error) {
    console.log(error);
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong!</p>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Using Error Boundaries

import ErrorBoundary from './components/ErrorBoundary';
import Users from './components/Users';

function App() {
  return (
    <ErrorBoundary>
      <Users />
    </ErrorBoundary>
  );
}
Error boundaries catch errors in child components during rendering, in lifecycle methods, and in constructors. They don’t catch errors in event handlers.

Class vs Functional Components

import { Component } from 'react';

class User extends Component {
  constructor() {
    super();
    this.state = { showDetails: false };
  }

  toggleDetailsHandler = () => {
    this.setState((prevState) => ({
      showDetails: !prevState.showDetails
    }));
  }

  render() {
    return (
      <li>
        <h2>{this.props.name}</h2>
        <button onClick={this.toggleDetailsHandler}>
          Toggle Details
        </button>
        {this.state.showDetails && <p>User details...</p>}
      </li>
    );
  }
}

Context in Class Components

Access context using this.context:
import { Component } from 'react';
import UsersContext from '../store/users-context';

class UserFinder extends Component {
  static contextType = UsersContext;

  componentDidMount() {
    // Access context via this.context
    this.setState({ filteredUsers: this.context.users });
  }

  render() {
    return <div>...</div>;
  }
}
A class component can only consume one context. Use Context.Consumer for multiple contexts.

When to Use Class Components

Error Boundaries

Currently, error boundaries must be class components. No hook equivalent exists yet.

Legacy Code

When working with existing codebases that use class components.

Migration

Understanding classes helps when migrating old code to functional components.

Learning React

Understanding React’s evolution and core concepts.

Lifecycle Comparison

Class: constructor()render()componentDidMount()Functional: Component function executes → useEffect runs after render
Class: render()componentDidUpdate(prevProps, prevState)Functional: Component re-executes → useEffect runs if dependencies changed
Class: componentWillUnmount()Functional: Cleanup function returned from useEffect

Key Differences

FeatureClass ComponentsFunctional Components
SyntaxES6 classesJavaScript functions
Statethis.state and setState()useState hook
LifecycleLifecycle methodsuseEffect hook
Contextthis.context or ConsumeruseContext hook
PerformanceSlightly more overheadGenerally lighter
FutureLegacy approachRecommended approach
Source Code: Section 14 - Class-based Components

Build docs developers (and LLMs) love