React.Component is the base class for creating components using inheritance in React. Before the introduction of hooks in version 16.8, class components were the only way to manage state and lifecycle. According to React, 2024, class components are still supported, although functional components with hooks are recommended for new projects.
Key Takeaways
React.Component is an abstract class that provides built-in methods for working with state, props, and the component lifecycle. To create a class component, you define a class that extends React.Component and implement the render method.
React.Component appeared in the very first version of React (2013) and remained the primary way to create components for five years. The React ecosystem was built around classes: higher-order components (HOC), render props, context — all these patterns were implemented using class syntax.
With the release of React 16.8 in 2019, the React team introduced hooks as an alternative to classes. Hooks solved the same problems — state, effects, context — but without inheritance and complex context binding. React.Component remains fully supported, but the functional approach is recommended for new projects.
The minimal class component is a class with a single render method that returns a React element. render is the only required method. It must be a pure function of this.props and this.state: the same set of props and state produces the same result.
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
The constructor executes before mounting. It calls super(props) to pass props to the parent class and initializes this.state. Using the constructor for other purposes — subscriptions, API calls — is not recommended; componentDidMount is the right place for that.
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <p>Count: {this.state.count}</p>;
}
}
this.state is an object containing the component's data. Mutating it directly (this.state.count = 1) is forbidden — it does not trigger a re-render and breaks predictability. State must be updated through this.setState, which creates a new state object and triggers a re-render.
setState can accept an object or a function. The functional form is preferred when the new state depends on the previous one: this.setState(prev => ({ count: prev.count + 1 })). React may batch multiple setState calls together for performance optimization.
class Toggle extends Component {
constructor(props) {
super(props);
this.state = { isOn: false };
}
handleClick() {
this.setState(prev => ({ isOn: !prev.isOn }));
}
render() {
return (
<button onClick={() => this.handleClick()}>
{this.state.isOn ? 'ON' : 'OFF'}
</button>
);
}
}
An important property of setState is that it is asynchronous. After calling setState, the state does not update immediately. If you need to perform an action after the update, pass a callback as the second argument: this.setState(newState, () => console.log(this.state)).
Class components have predefined methods that React calls at specific moments. Mounting — constructor → render → componentDidMount. Updating — render → componentDidUpdate. Unmounting — componentWillUnmount.
| Method | Phase | Purpose |
|---|---|---|
| constructor | Mounting | Initialize state and bind methods |
| render | Every time | Return React elements (required) |
| componentDidMount | After mounting | API requests, subscriptions, timers |
| componentDidUpdate | After update | React to prop/state changes |
| componentWillUnmount | Before removal | Clean up subscriptions, timers, event listeners |
componentDidMount is the primary place for side effects after the first render. Here you perform HTTP requests, establish WebSocket connections, and start timers. If you do not clean up the subscription in componentWillUnmount, a memory leak occurs.
class UserProfile extends Component {
componentDidMount() {
fetch(`/api/users/${this.props.userId}`)
.then(res => res.json())
.then(data => this.setState({ user: data }));
}
componentWillUnmount() {
// Cancel request on unmount
}
render() {
return <div>{this.state.user?.name}</div>;
}
}
In JavaScript, class methods are not bound to the instance by default. If you pass a method as a callback — to an event handler, to a timer — this will be undefined (in strict mode) or window (without strict mode). Without binding, this.setState will throw an error.
There are three ways to bind: bind in the constructor, an arrow function in the callback, and a class field with an arrow function. The first method is the official React recommendation: bind once in the constructor, without recreating the function on every render.
// Method 1: bind in constructor (recommended)
class MyComponent extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
Class fields — modern syntax with automatic binding. An arrow function in a class property captures this from the constructor's scope. The downside is that the method is created on each instance, not on the prototype, which increases memory usage for hundreds of components of the same type.
The choice between class and function is not just a matter of syntax. Functional components with hooks solve problems inherent to classes: duplication of logic across different lifecycle methods (componentDidMount + componentDidUpdate), complexity with this, and the inability to extract related logic into a reusable block.
Functional components are easier to test — they have no internal state (which before hooks was only available in classes) and do not require DOM mounting to verify callbacks. React recommends functional components for all new projects; documentation and examples are written using them.
Class components remain the best choice when working with legacy code. If a project was started before 2019, it likely contains thousands of class components. Rewriting all of them to hooks in a single sprint is risky. React guarantees backward compatibility: class components will not be removed.
Gradual migration begins with analyzing the component. Simple stateless components are translated first — they are trivially replaced with functions. Next — components with one or two state fields, where useState directly replaces this.state and this.setState.
Complex components with multiple lifecycle methods are translated using useEffect. Logic from componentDidMount + componentDidUpdate is split into separate useEffect calls with different dependencies. componentWillUnmount is replaced by a return function inside useEffect. For complex state logic, use useReducer.
Automated migration tools — @phenomnomnominal/ts-react-class-to-hook or codemod scripts — can automatically convert simple classes. For complex cases, migration remains manual work requiring an understanding of the source component's logic.
Frequently Asked Questions
Yes. super(props) passes props to the parent React.Component. Without this call, this.props will be undefined inside the constructor. In render and lifecycle methods, props are available even without super, but this is incidental implementation behavior.
React batches multiple setState calls into a single batch to reduce unnecessary re-renders. If setState were synchronous, each call would trigger an immediate render, reducing performance during mass updates.
No. Hooks only work in functional components. For projects with class components, all the old patterns are available: render props, HOC, and Consumer for context. Migrating to hooks requires converting the class into a function.
React.PureComponent implements shouldComponentUpdate with a shallow comparison of props and state. Unlike Component, which always re-renders on setState, PureComponent skips re-rendering if the data has not changed. This is an optimization for components without complex nesting.
Use componentDidCatch and getDerivedStateFromError. componentDidCatch(error, info) logs the error. getDerivedStateFromError updates state to display a fallback UI. These methods only work in class components — functions need a wrapping ErrorBoundary component.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also