State in Class Components
Class components store local state on this.state.
State represents data that can change over time and affects what the component renders.
Initial State
You can initialize state in the constructor.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <p>Count: {this.state.count}</p>;
}
}Many projects use class fields instead.
class Counter extends React.Component {
state = { count: 0 };
render() {
return <p>Count: {this.state.count}</p>;
}
}Updating State
Never assign to this.state after initialization.
// Bad
this.state.count = this.state.count + 1;Use this.setState.
this.setState({ count: this.state.count + 1 });React schedules a re-render after state changes.
Functional setState
When the next state depends on the previous state, use the functional form.
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.setState((previousState) => ({
count: previousState.count + 1,
}));
};
render() {
return <button onClick={this.increment}>{this.state.count}</button>;
}
}This avoids bugs when React batches updates.
Batching and Stale State
Multiple state updates may be batched.
incrementTwice = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
};This may only increment once because both updates read the same this.state.count.
Use functional updates:
incrementTwice = () => {
this.setState((state) => ({ count: state.count + 1 }));
this.setState((state) => ({ count: state.count + 1 }));
};setState Merges Objects
In class components, setState shallowly merges the object you provide.
this.state = {
name: "Ava",
email: "ava@example.com",
};
this.setState({ email: "new@example.com" });After this update, name is still present.
This differs from useState, where setting object state replaces the whole value.
Nested State Still Needs Immutability
The merge is shallow. Nested objects must still be copied.
this.setState((state) => ({
user: {
...state.user,
address: {
...state.user.address,
city: "Pune",
},
},
}));Mutating nested objects can cause confusing bugs.
setState Callback
setState accepts an optional callback that runs after the update is applied.
this.setState({ saved: true }, () => {
console.log("Saved state is now visible in the component");
});Use this sparingly. Many side effects belong in componentDidUpdate.
Common Mistakes
- Directly mutating
this.state. - Reading stale state when doing multiple updates.
- Forgetting that class
setStateshallowly merges objects. - Assuming nested objects are deeply merged.
- Storing derived data that can be calculated during render.
Why is functional setState recommended when the next value depends on the previous value?
Recap
Class state lives on this.state and is updated with this.setState.
Use functional updates for counters, toggles, arrays, and any update based on previous state.
Practice
Build a class component with items and filter in state.
Add a button that appends an item without mutating the existing array, and an input that updates only the filter.