React Hooks: Modern State Management

Hero image for React Hooks: Modern State Management. Image by Joe Dudeck.
Hero image for 'React Hooks: Modern State Management.' Image by Joe Dudeck.

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

FeatureClass componentsWith hooks
State Managementthis.state & setStateuseState Hook
Side EffectsLifecycle methodsuseEffect Hook
Code ComplexityMore boilerplateConcise and readable
PerformanceSlightly heavierLighter, and avoids unnecessary rerenders

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 futureproof 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 classbased 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.
  • useState replaces this.state, making state management cleaner and easier.
  • useEffect replaces 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.


Categories:

  1. Architecture
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. React