Class components were the original way to create stateful components in React before the introduction of hooks. While functional components with hooks are now preferred, understanding class components remains important for maintaining legacy code.
1. What Are Class Components?
Class components are ES6 classes that:
- Extend
React.Component
- Have a
render()
method that returns JSX - Can maintain internal state
- Have access to lifecycle methods
import React from 'react';
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
2. Key Features of Class Components
Props
class UserProfile extends React.Component {
render() {
const { name, age, isAdmin } = this.props;
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
{isAdmin && <button>Edit Profile</button>}
</div>
);
}
}
State Management
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
Lifecycle Methods
class DataFetcher extends React.Component {
state = { data: null };
componentDidMount() {
fetch(`/api/users/${this.props.userId}`)
.then(res => res.json())
.then(data => this.setState({ data }));
}
componentDidUpdate(prevProps) {
if (this.props.userId !== prevProps.userId) {
this.fetchData();
}
}
componentWillUnmount() {
// Clean up any subscriptions
}
render() {
return <div>{this.state.data ? this.state.data.name : 'Loading...'}</div>;
}
}
3. Complete Lifecycle Overview
Mounting Phase
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
Updating Phase
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
Unmounting Phase
componentWillUnmount()
Error Handling
static getDerivedStateFromError()
componentDidCatch()
4. When to Use Class Components Today
While functional components are preferred, class components are still necessary for:
- Error boundaries (no hook equivalent exists)
- Legacy codebases that haven’t migrated to hooks
- Certain third-party libraries that expect class components
5. Converting Class Components to Functional Components
Class Component
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = { todos: [], input: '' };
}
componentDidMount() {
const saved = localStorage.getItem('todos');
if (saved) this.setState({ todos: JSON.parse(saved) });
}
componentDidUpdate() {
localStorage.setItem('todos', JSON.stringify(this.state.todos));
}
addTodo = () => {
if (this.state.input.trim()) {
this.setState({
todos: [...this.state.todos, { text: this.state.input, completed: false }],
input: ''
});
}
};
render() {
return (
<div>
<input
value={this.state.input}
onChange={e => this.setState({ input: e.target.value })}
placeholder="Add todo..."
/>
<button onClick={this.addTodo}>Add</button>
<ul>
{this.state.todos.map((todo, i) => (
<li key={i}>{todo.text}</li>
))}
</ul>
</div>
);
}
}
Equivalent Functional Component
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
const saved = localStorage.getItem('todos');
if (saved) setTodos(JSON.parse(saved));
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { text: input, completed: false }]);
setInput('');
}
};
return (
<div>
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Add todo..."
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, i) => (
<li key={i}>{todo.text}</li>
))}
</ul>
</div>
);
}
6. Key Differences from Functional Components
Feature | Class Components | Functional Components |
---|---|---|
Syntax | ES6 class | JavaScript function |
State | this.state and setState() | useState hook |
Lifecycle | Methods like componentDidMount | useEffect hook |
this binding | Requires binding or arrow functions | Not needed |
Performance | Slightly heavier | Slightly lighter |
Reusability | Mixins (deprecated) | Custom hooks |
7. Best Practices for Class Components
- Bind event handlers in constructor or use arrow functions
- Use
shouldComponentUpdate
for performance optimizations - Clean up resources in
componentWillUnmount
- Avoid
setState
inrender
(causes infinite loops) - Consider migrating to functional components for new code