React ES6 Classes

ES6 classes provide the syntax that class-based React components use to hold state and lifecycle methods.

Classes are syntax over prototypes

JavaScript has always used prototypal inheritance, but ES6 (2015) introduced the 'class' keyword as cleaner syntax over that same mechanism. A class is not a new type system — under the hood, methods still live on the prototype object, and 'new MyClass()' still creates an object whose prototype is 'MyClass.prototype'. React's older class components rely entirely on this syntax, so understanding it is essential for reading legacy React code.

A class declaration groups a constructor and a set of methods under one name. Unlike function declarations, class declarations are not hoisted in a usable way — you must define a class before referencing it.

A basic ES6 class

class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

function App() {
  const g = new Greeter('Ava');
  console.log(g.greet()); // "Hello, Ava!"

  return <p>{g.greet()}</p>;
}

export default App;

Inheritance with extends and super

The 'extends' keyword lets one class inherit from another, and 'super(...)' calls the parent constructor. This is exactly the pattern React.Component relies on: every class component extends React.Component and must call super(props) before using 'this' in its constructor.

extends and super

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} makes a sound.`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // must run before using `this`
    this.breed = breed;
  }
  speak() {
    return `${this.name} barks.`;
  }
}

function App() {
  const d = new Dog('Rex', 'Labrador');
  console.log(d.speak()); // "Rex barks."

  return <p>{d.speak()}</p>;
}

export default App;

Class components in React

Before Hooks existed (React 16.8 and earlier code), stateful components were written as classes. State lived on 'this.state', updates went through 'this.setState()', and lifecycle events like mounting or updating were separate named methods rather than useEffect calls.

A minimal class component

import React from 'react';

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

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

  componentDidMount() {
    console.log('Counter mounted');
  }

  render() {
    return (
      <button onClick={this.increment}>
        Clicked {this.state.count} times
      </button>
    );
  }
}

export default Counter;
Note: The 'increment' method above is defined as a class field using an arrow function. This is not core ES6 — it's a class-fields proposal that Babel and modern JS engines support — but it's the idiomatic way to bind 'this' automatically in class components.

Classes vs. function components

ConceptClass componentFunction component (Hooks)
Statethis.state / this.setStateuseState
Mount/update logiccomponentDidMount / componentDidUpdateuseEffect
CleanupcomponentWillUnmountreturn a cleanup function from useEffect
'this' bindingRequired, easy to get wrongNot applicable
Note: Without arrow-function class fields, methods passed as event handlers (e.g. onClick={this.increment}) lose their 'this' binding, because JavaScript methods are not bound to their instance by default. The classic fix in constructor-based code is `this.increment = this.increment.bind(this);`.

Exercise: React ES6 Classes

Why must a component class in React use `extends React.Component`?