
Class vs. Functional Components in React

In React, we have two primary ways to create our components: class components and functional components. Whilst class components were the standard for many years, functional components have become increasingly popular, especially with the introduction of React Hooks.
In this article, I will explore the differences between class and functional components, their advantages and drawbacks, and when to use each approach. Hopefully, by the end, you will understand how to decide between them in different scenarios.
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.ComponentorReact.PureComponent. - Use
this.stateto manage local state. - Require lifecycle methods like
componentDidMountandcomponentDidUpdate. - Require binding for event handlers unless using arrow functions.
What are Functional Components?
Functional 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 functional component, using the useState Hook to manage its own state. It also renders a button that increments the count when clicked.
Key Characteristics of Functional Components
- Defined as functions that return JSX.
- Uses
useState,useEffect, and other Hooks for state and lifecycle behaviour. - Does not require
thiskeyword. - More concise and easier to read.
Comparing Class and Functional Components
| Feature | Class Components | Functional Components |
|---|---|---|
| State Management | this.state and setState | useState Hook |
| Lifecycle Methods | componentDidMount, etc. | useEffect Hook |
| Performance | Slightly heavier | Lighter and faster |
| Code Readability | More boilerplate | More concise |
| Best for Complex Logic | Yes | Now possible with hooks |
Error Boundaries in React
Error boundaries are special components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing the entire application. They are particularly useful for handling unexpected runtime errors in production.
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 catches errors from its child components and displays a fallback message instead of allowing the app to crash. 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. Functional 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 Functional Components When:
- You want simpler, more readable code.
- You need improved performance due to fewer re‑renders.
- You're using modern React best practices with Hooks.
- You want to ensure easier future maintenance, as React's development focus is now on functional components.
Should You Use Class Components in New Projects?
It is fair to say that functional components should be the default choice for the vast majority of new projects. They offer a more concise syntax, better performance, and greater flexibility with Hooks. The only remaining reason to use class components is for error boundaries, but even that may change in future React releases.
Wrapping up
With the introduction of Hooks, functional components now offer nearly all the capabilities of class components whilst being simpler, more concise and performant. As I've already said just above: for any modern React development or new projects, you should be using functional components as the default choice. Class components still serve a purpose in legacy codebases and for implementing error boundaries, but they are no longer the preferred approach for new projects.
Key Takeaways
- Class components use
this.stateand lifecycle methods, making them more verbose. - Functional components with Hooks can manage state and side effects in a simpler way.
- Functional components are now the preferred approach for most new React projects.
- Understanding both helps when working with legacy React applications.
By understanding these differences, you can make informed decisions when building React applications.
Related Articles

Advanced Techniques for Responsive Web Design. 
React: Functional, Class, and Pure Components. React: Functional, Class, and Pure Components

Function Declarations vs. Function Expressions vs. Arrow Functions. Function Declarations vs. Function Expressions vs. Arrow Functions

Closures in JavaScript: The Key to Lexical Scope. Closures in JavaScript: The Key to Lexical Scope

Optimising Website Performance with HTML, CSS, and JavaScript. Optimising Website Performance with HTML, CSS, and JavaScript

Testing Vue Components with Vue Test Utils. Testing Vue Components with Vue Test Utils

Use Greater‑Than and Less‑Than Symbols in JSX. Use Greater‑Than and Less‑Than Symbols in JSX

The arguments Object vs. Rest Parameters in JavaScript. The
argumentsObject vs. Rest Parameters in JavaScript
LeetCode: Reversing Integers with JavaScript. LeetCode: Reversing Integers with JavaScript

Classes in JavaScript: An Introduction. Classes in JavaScript: An Introduction

Mastering JavaScript Iterators and Generators. Mastering JavaScript Iterators and Generators
Use Chrome's Developer Tools to Track Element Focus. Use Chrome's Developer Tools to Track Element Focus