Overview

History

Class components were widely used before the introduction of hooks (with React 16.8, in February 2019)

Today focus is shifting away from class components and towards hooks

Class components vs hooks

reasons for using hooks:

  • reducing complexity
  • easier code re-use / modularity
  • avoiding problems with this

reasons for using class components:

  • avoiding problem with outdated state (stale closures)
  • instance variables (in class components) are simpler than refs (in function components)

Class component example

import { Component } from 'react';

class App extends Component {
  constructor(props) {
    // ...
    this.state = { name: 'World' };
  }

  render() {
    return <div>Hello, {this.state.name}!</div>;
  }
}

export default App;

Props

Props

Props can be accessed via this.props:

type TodoItemProps = {
  todo: Todo;
  onDelete: (id: number) => void;
};

class TodoItem extends Component<TodoItemProps> {
  render() {
    return (
      <li>
        {this.props.todo.completed ? 'DONE: ' : 'TODO: '}
        {this.props.todo.title}
      </li>
    );
  }
}

State

State

In class components, this.state represents the state.

this.state is always a JavaScript object which can have various entries (properties)

State changes happen via this.setState()

Structure of this.state

this.state is always an object:

{
  "todos": [],
  "loadingStatus": "idle"
}

Type declarations

type TodoAppProps = {};
type TodoAppState = {
  todo: Array<Todo>;
  loadingStatus: string;
};

class TodoApp extends Component<
  TodoAppProps,
  TodoAppState
> {
  // ...
}

Initializing the state

The state must be initialized in the constructor

The constructor will also receive the component's props as an argument

Initializing the state

constructor(props: TodoAppProps) {
  super(props);
  this.state = {
    todos: [],
    loadingStatus: "idle",
  }
}

JavaScript requires calling the constructor of the parent class (Component) via super() in the constructor

Changing state

this.setState({ loadingStatus: 'loading' });

setState will change all specified entries and leave the rest unchanged

Problems with "this"

Problems with "this"

in class components - especially in event handlers - this may sometimes be set incorrectly

Problems with "this"

generic problem: method calls without method syntax:

class Foo {
  constructor() {
    this.message = 'hello';
  }
  greet() {
    console.log(this.message);
  }
}
const foo = new Foo();
foo.greet(); // ok
const greet = foo.greet;
greet(); // not ok ("this" is undefined)

Problems with "this"

problem inside React renderings:

class Foo extends Component {
  // ...

  greet() {
    console.log(this.message);
  }
  render() {
    return <button onClick={this.greet}>hello</button>;
  }
}

Problems with "this"

solution A: arrow methods:

class Foo extends Component {
  // ...
  greet = () => {
    console.log(this.message);
  };
  // ...
}

solution B: binding the method in the constructor:

  constructor() {
    // ...
    this.greet = this.greet.bind(this);
  }

Exercises

Exercises

convert existing components (e.g. AddTodo) from function components to class components