Class vs. Functional Components in React

Abstract image used to represent Class vs. Functional Components in React
Image by Elimende Inagella.

Function components and React Hooks are the sensible default for most new React work, but that does not make class components irrelevant. Existing applications still contain them, and classbased error boundaries remain part of React's API. The decision is therefore less about declaring a winner and more about understanding the code you are building or maintaining.


What are Class Components?

Class components are ES6 classes that extend React.Component. They allow you to manage state and lifecycle methods within a single component.

An Example of a Class Component:

import React, { Component } from "react";

class Counter extends Component<{ initialCount: number }, { count: number }> {
  constructor(props: { initialCount: number }) {
    super(props);
    this.state = { count: props.initialCount };
  }

  increment = () => {
    this.setState((prevState) => ({ count: prevState.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;

Here we have a fairly basic example of a class component that manages its own state using this.state and updates it with this.setState(). It renders a button which increments the count when clicked.

Key Characteristics of Class Components

  • Must extend React.Component or React.PureComponent.
  • Use this.state to manage local state.
  • Can use lifecycle methods like componentDidMount and componentDidUpdate.
  • Require binding for event handlers unless using arrow functions.

What are Function Components?

Function components are simpler and are defined as functions that return JSX. Before React Hooks, they were limited to stateless components, but Hooks have enabled them to handle state and side effects in much the same way (albeit simpler) than a class component can.

Example of a Functional Component

import React, { useState } from "react";

const Counter: React.FC<{ initialCount: number }> = ({ initialCount }) => {
  const [count, setCount] = useState(initialCount);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;

This is basically the same component as we discussed above, except that it is a function component, using the useState Hook to manage its own state. It also renders a button that increments the count when clicked.

Key Characteristics of Function Components

  • Defined as functions that return JSX.
  • Uses useState, useEffect, and other Hooks for state and lifecycle behaviour.
  • Does not require this keyword.
  • More concise and easier to read.

Comparing Class and Function Components

FeatureClass ComponentsFunction Components
State Managementthis.state and setStateuseState Hook
Lifecycle MethodscomponentDidMount, etc.useEffect Hook
PerformanceNo inherent disadvantageNo inherent advantage
Code ReadabilityMore boilerplateMore concise
Best for Complex LogicYesNow possible with hooks

Error Boundaries in React

Error boundaries can show a fallback when a descendant throws during rendering, in a constructor or in a lifecycle method. They do not catch ordinary eventhandler errors, asynchronous callbacks, serverrendering errors or errors in the boundary itself. Those failures need handling where they occur; an error boundary does not replace that work.

An Example of an Error Boundary Component

To offer a more concrete example:

import React, { Component, ErrorInfo } from "react";

class ErrorBoundary extends Component<{ children: React.ReactNode }, { hasError: boolean }> {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(): { hasError: boolean } {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Caught error:", error, errorInfo);
  }

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

export default ErrorBoundary;

Here, ErrorBoundary displays a fallback for the descendant rendering and lifecycle errors described above. As of now, only class components can be used as error boundaries, which is one of the (very) few remaining reasons to use class components in React rather than their functional counterparts.


When to Use Class vs. Function Components

Use Class Components When:

  • You're maintaining an older React codebase that already uses classes.
  • You need error boundaries (currently, only class components can be error boundaries).

Use Function Components When:

  • You want simpler, more readable code.
  • You want to compose stateful logic through Hooks; render frequency still depends on parents, state, context and memoisation.
  • You're using modern React best practices with Hooks.
  • You want to ensure easier future maintenance, as React's development focus is now on function components.

Should You Use Class Components in New Projects?

Function components are a sensible default for the vast majority of new projects because Hooks compose stateful logic without class syntax. That choice does not guarantee better performance or fewer renders. Class error boundaries remain a frameworklevel reason to retain classes, alongside maintaining existing classbased code.


Wrapping Up

With the introduction of Hooks, function components offer nearly all the capabilities previously associated with classes and often allow more concise composition. The syntax itself is not a performance optimisation. Function components are the default for new work, whilst classes remain relevant in existing codebases and for classbased error boundaries.

Key Takeaways

  • Class components use this.state and lifecycle methods, making them more verbose.
  • Function components with Hooks can manage state and side effects in a simpler way.
  • Function components are now the preferred approach for most new React projects.
  • Understanding both helps when working with legacy React applications.

Function components are the normal starting point for new React work. Class components remain supported, still make sense in existing classbased code, and remain necessary for an error boundary that implements componentDidCatch because React has no direct functioncomponent equivalent. Neither syntax is a performance optimisation by itself.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.