
React Hooks: Modern State Management

State management is a core concept in React, enabling components to store and manage dynamic data. Before React 16.8, class components were required for stateful logic. Read more about the differences between Class and Functional components in my article here. However, with the introduction of Hooks just a couple of months ago, functional components have gained the ability to manage state and side effects themselves, too.
In this article, I will explore how React Hooks modernise state management, their benefits, and how they compare to previous approaches. Hopefully, you will understand how Hooks changes the way we build React applications.
What are React Hooks?
They are fairly new, so you can be forgiven if you've not yet had much exposure to them. Basically, React Hooks are functions which allow functional components to use state and lifecycle features that were previously only available in class components. They were introduced in React 16.8 and are now considered the preferred approach for managing state in new React applications.
Some of the most commonly used Hooks include:
useState– Manages local component state.useEffect– Handles side effects, replacing lifecycle methods.useContext– Accesses context without nested consumers.
Hooks remove the need for class components almost entirely, making React applications more concise and readable.
Managing State with useState
The useState Hook allows functional components to maintain local state without needing a class.
Example Before Hooks (Class Component)
Here's an example of what you might be familiar with from managing local state in a class component:
import React, { Component } from "react";class Counter extends Component<{}, { count: number }> { 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> ); }}export default Counter;This is a fairly simple component that uses its state to store a count which is then updated incrementally every time the button is clicked on.
Example Using useState
Here is the same component, but using useState as a functional component instead:
import React, { useState } from "react";const Counter: React.FC = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> );};export default Counter;Why useState Is Better
As you can see from the examples above, useState (and being a functional component) is much smaller and more intuitive. This new use of useState...
- Eliminates the need for class components.
- More concise and readable syntax.
- State updates are explicit and easier to understand.
Handling Side Effects with useEffect
Before Hooks, class components used lifecycle methods like componentDidMount and componentDidUpdate to manage side effects (e.g., data fetching, subscriptions). useEffect replaces these methods in functional components by reacting to a change within their dependency array (or just when the component mounts if the array is empty).
Example Before Hooks (Class Component)
Let's take a look at a fairly simple example where a component fetches data from an API when it mounts, and then stores it in local state to render it inside a <p> element.
As a class component, this might look something like:
import React, { Component } from "react";class FetchData extends Component<{}, { data: string }> { constructor(props: {}) { super(props); this.state = { data: "" }; } componentDidMount() { fetch("https://api.example.com/data") .then((response) => response.json()) .then((data) => this.setState({ data })); } render() { return <p>{this.state.data}</p>; }}export default FetchData;Example Using useEffect
Here's that same component using hooks (and as a functional component):
import React, { useState, useEffect } from "react";const FetchData: React.FC = () => { const [data, setData] = useState(""); useEffect(() => { fetch("https://api.example.com/data") .then((response) => response.json()) .then((data) => setData(data)); }, []); return <p>{data}</p>;};export default FetchData;Why useEffect Is Better
- Consolidates lifecycle logic in one place.
- Avoids duplication across lifecycle methods.
- Automatically cleans up effects when dependencies change.
Comparing Hooks to Class Components
| Feature | Class components | With hooks |
|---|---|---|
| State Management | this.state & setState | useState Hook |
| Side Effects | Lifecycle methods | useEffect Hook |
| Code Complexity | More boilerplate | Concise and readable |
| Performance | Slightly heavier | Lighter, and avoids unnecessary re‑renders |
Should You Use Hooks?
As of right now, React Hooks are stable and recommended for new projects. Whilst class components still work, they are no longer necessary in most cases. Hooks provide a more modern and efficient way to manage state and side effects.
Use Hooks If:
- You are starting a new React project.
- You want cleaner, more readable components.
- You want to future‑proof your code as React evolves.
Use Class Components If:
- You are maintaining an older codebase that relies on them.
- You need error boundaries (as of right now, they still require class components although that might change).
Wrapping up
React Hooks have revolutionised state management, making functional components as powerful as class components. By replacing class‑based state and lifecycle management with useState and useEffect, Hooks simplify code, improve readability, and enhance the performance of our applications.
Key Takeaways
- Hooks were introduced in React 16.8 (earlier this year) to bring state and lifecycle features to functional components.
useStatereplacesthis.state, making state management cleaner and easier.useEffectreplaces lifecycle methods, simplifying side effect management.- Hooks are now the preferred approach for state management in React, though class components are still valid for legacy applications.
By understanding Hooks, you can write more modern, maintainable React applications.
Related Articles

Class vs. Functional Components in React. 
Why We Use an Empty Dependency Array in React's useEffect Hook. Why We Use an Empty Dependency Array in React's
useEffectHook
Angular Change Detection: How It Works and How to Optimise It. Angular Change Detection: How It Works and How to Optimise It

GetStaticProps vs. getServerSideProps in Next.js. getStaticPropsvs.getServerSidePropsin Next.js
Template Literals in JavaScript: Writing Multi‑Line Strings. Template Literals in JavaScript: Writing Multi‑Line Strings

Understanding Arrow Functions in JavaScript. Understanding Arrow Functions in JavaScript

Trigonometric Functions in CSS. Trigonometric Functions in CSS

Using Container Queries in CSS. Using Container Queries in CSS

Renaming and Destructuring Variables in ES6. Renaming and Destructuring Variables in ES6

Advanced Techniques for Responsive Web Design. Advanced Techniques for Responsive Web Design

3Sum in JavaScript: Two Pointers After Sorting. 3Sum in JavaScript: Two Pointers After Sorting

Why Next.js Middleware Might Be Unavailable with Pages Router. Why Next.js Middleware Might Be Unavailable with Pages Router