Skip to main content

Class Components in React: Part 1 Guide

Class components are ES6 classes that extend React.Component and were the primary way to manage state before Hooks arrived in React 16.8. Understanding them is essential for maintaining legacy codebases and for appreciating why modern functional components with Hooks are simpler. This guide covers constructors, this.state, this.setState, and practical state management patterns.

Key Takeaways

  • Class components must extend React.Component and include a render() method to return JSX
  • State is initialized in the constructor() using this.state = { ... } and updated with this.setState()
  • The this keyword accesses both this.props (from parent) and this.state (internal state)
  • Arrow functions auto-bind this, avoiding the common binding pitfall that made functional components preferable
  • Before React 16.8, class components were the only way to use lifecycle methods and local state

Prerequisites

Before starting, ensure you understand:

  • Basic JavaScript ES6 classes and the extends keyword
  • The this keyword in JavaScript
  • React props and component composition basics
  • JSX syntax and component rendering

The Core Concepts of Class Components

A class component is an ES6 class that extends React.Component. It must include a render() method that returns JSX. Before React 16.8 (the release of Hooks), class components were the only way to manage local state and use lifecycle methods in a component. The three essential concepts are:

  • extends React.Component: This gives a regular JavaScript class access to React features like state, setState, and lifecycle methods.
  • The render() Method: The only required method in a class component; returns the component's UI as JSX.
  • this.state and this.props: State (internal) and props (from parent) are both accessed via the this keyword.

Deep Dive: Anatomy of a Class Component

Let's create a simple class component to understand its structure. The most basic class component displays props without managing internal state:

// components/Welcome.jsx
import React, { Component } from 'react';

class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

export default Welcome;

Code Breakdown:

  1. import React, { Component } from 'react'; — Import the Component class to extend React functionality.
  2. class Welcome extends Component { ... } — Declare a class that extends React.Component, inheriting React's methods.
  3. render() { ... } — Return JSX describing the UI; this is the only required method.
  4. return <h1>Hello, {this.props.name}</h1>; — Access props via this.props.name (passed by the parent component).

When you use <Welcome name="Alice" />, React renders <h1>Hello, Alice</h1>.

Managing State in a Class Component: The Counter Example

The primary reason for using class components was to manage internal state. Here's a counter component that demonstrates the full pattern: initialization in the constructor, state updates via this.setState, and rendering the current state.

Goal: Create a counter that increments when a button is clicked.

Plan:

  1. Initialize state in the constructor
  2. Create a method to update the state using this.setState
  3. Render the state value and a button to trigger updates
// components/Counter.jsx
import React, { Component } from 'react';

class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};

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

export default Counter;

Detailed Walkthrough:

  • constructor(props) — Called once when the component mounts. Always call super(props) first to properly initialize the parent React.Component class.
  • this.state = { count: 0 }; — Initialize state as an object. This is the only place where you assign this.state directly; all updates must use this.setState().
  • incrementCount = () => { ... } — An arrow function automatically binds this, solving the binding confusion that plagued early class component code.
  • this.setState({ count: this.state.count + 1 }) — Notify React that state changed; React schedules a re-render and updates the DOM.
  • this.state.count — Access the current state value in the render method; when state updates, render runs again with new values.

Why this Binding Matters

In class components, the this keyword refers to the component instance. Event handlers (like onClick) need correct this binding to access this.state and this.props. Using arrow function syntax (incrementCount = () => { ... }) automatically binds this to the component instance. Without this pattern or explicit binding in the constructor (e.g., this.incrementCount = this.incrementCount.bind(this);), the button click handler fails because this is undefined.

Best Practices and Anti-Patterns

Best Practices (for Class Components):

  • Initialize State in Constructor Only — The constructor is the only place where you assign this.state directly. In all other methods, use this.setState().
  • Use setState for Updates — Never mutate state directly. this.setState() tells React the state changed and triggers a re-render.
  • Bind this or Use Arrow Functions — If not using arrow functions, bind methods in the constructor: this.incrementCount = this.incrementCount.bind(this);.
  • Keep State Minimal — Store only data that changes and triggers UI updates; avoid storing derived or static values.

Anti-Patterns (What to Avoid):

  • Mutating this.state Directlythis.state.count = 1; will NOT trigger a re-render and causes bugs. Always use this.setState().
  • Forgetting super(props) — Failing to call super(props) in the constructor prevents proper initialization and causes errors.
  • Using index as Key — In loops, never use array index as the React key in class components (same rule for functional components); it breaks reconciliation when items reorder.

Frequently Asked Questions

What is the difference between class components and functional components?

Class components are ES6 classes extending React.Component with a render() method and lifecycle methods. Functional components are plain JavaScript functions returning JSX. After Hooks (React 16.8), functional components can do everything class components do, but with less boilerplate. Functional components are now the React standard.

When should I use class components instead of hooks?

In modern React projects, always prefer functional components with Hooks. Use class components only when maintaining legacy code, updating an old codebase that still uses them, or when you need a class component for specific legacy reasons. New projects should use functional components exclusively.

Do I have to bind methods in the constructor?

No. Using arrow function syntax (incrementCount = () => { ... }) auto-binds this without explicit constructor binding. Arrow functions are simpler and avoid the common this binding confusion that made class components unpopular before Hooks.

What happens if I mutate this.state directly instead of using setState?

The state object changes, but React has no way to know, so it doesn't re-render. Your component's UI stays out of sync with the state, causing bugs. Always use this.setState() so React detects the change and updates the DOM.

Can class components use Hooks?

No. Hooks (like useState, useEffect) only work in functional components. Class components have lifecycle methods instead. If you need to convert a class component to use Hooks, convert it to a functional component first.

Conclusion

Class components were React's primary way to build stateful components before Hooks. While they're no longer the standard, understanding them is crucial for maintaining older codebases and for appreciating why modern functional components are simpler. The key concepts are:

  • Class components extend React.Component and must include a render() method
  • State is initialized in constructor and updated with this.setState()
  • Props are accessed via this.props; state via this.state
  • Arrow functions auto-bind this, avoiding confusing binding issues
  • Modern React uses functional components with Hooks instead

Next: In Part 2, you'll explore lifecycle methods like componentDidMount and componentWillUnmount, which gave class components their power before Hooks.

Further Reading