React Class Components
Before Hooks existed, React components were written as ES6 classes with their own internal state and lifecycle methods, and you'll still meet this style in older codebases today.
Why Class Components Still Matter
React shipped with class-based components for its first several years. Function components with Hooks are now the default way to write new code, but a huge amount of production React — especially in enterprise apps, older libraries, and error boundary implementations — still uses classes. Understanding the class syntax lets you read, maintain, and gradually migrate that code.
Defining a Class Component
A class component is a JavaScript class that extends React.Component and implements a render() method returning JSX. The class receives props automatically through its constructor and through this.props anywhere in the class body.
A Basic Class Component
import React from 'react';
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
export default Greeting;State with this.state and this.setState
Class components keep local state as a single object assigned to this.state, usually initialized in the constructor. You never mutate this.state directly; instead you call this.setState(), which merges the given fields into the existing state object and schedules a re-render.
A Counter Using State
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((prevState) => ({ count: prevState.count + 1 }));
}
render() {
return (
<button onClick={this.handleClick}>
Clicked {this.state.count} times
</button>
);
}
}
export default Counter;Binding Event Handlers
Inside a class, regular methods do not automatically bind 'this' to the component instance. If you pass this.handleClick directly to onClick without binding it, calling this.setState inside the handler throws because 'this' is undefined. The classic fixes are binding in the constructor (as above), using an arrow function class property, or wrapping the call in an inline arrow function in render.
- Bind in the constructor: this.handleClick = this.handleClick.bind(this)
- Use a class field arrow function: handleClick = () => { ... }
- Wrap at call site: onClick={() => this.handleClick()}
- Avoid re-binding inside render for performance-sensitive lists
Lifecycle Basics
Class components expose lifecycle methods that fire at specific points in a component's existence: mounting, updating, and unmounting. The three you'll use most often are componentDidMount, componentDidUpdate, and componentWillUnmount.
Fetching Data on Mount
import React from 'react';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = { user: null };
}
componentDidMount() {
fetch(`/api/users/${this.props.userId}`)
.then((res) => res.json())
.then((user) => this.setState({ user }))
.catch(() => {
// No backend available in this demo, so fall back to sample data
// instead of leaving the component stuck on "Loading...".
this.setState({ user: { name: 'Demo User' } });
});
}
render() {
if (!this.state.user) return <p>Loading...</p>;
return <h2>{this.state.user.name}</h2>;
}
}
UserProfile.defaultProps = {
userId: 1,
};
export default UserProfile;Exercise: React Class Components
What method must every class component define in order to display UI?