Sitemap

From Class Components to React Hooks: A Developer’s Guide

5 min readMay 4, 2025

--

As someone who has been working with React for many years, I’ve witnessed its evolution from the early days of class components to the modern functional components and hooks that we use today. React hooks, introduced in version 16.8, brought a massive shift in how we manage state, side effects, and lifecycle methods in functional components. In this blog post, I’m going to walk you through a guide that not only serves as a reference for understanding React hooks but also compares them with the classic class components we used before. If you’re still getting familiar with React hooks or making the transition, this post will help clarify their purpose and provide practical examples.

A Little History: React Before Hooks

Before the introduction of hooks, React development relied heavily on class components. These components were the cornerstone of React applications for a long time. They used lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount to handle side effects and other operations. State management was handled through the this.state object, and the this.setState method was used to update the state.

However, with the introduction of functional components and React hooks, we gained the ability to use features like state, side effects, and refs, which were previously only available in class components, all within simpler, cleaner function-based components.

The Advent of Hooks

React hooks are functions that allow functional components to “hook into” React features, such as state and lifecycle methods. These hooks have made functional components much more powerful and easier to work with compared to class components.

Let’s compare some commonly used hooks with their class component counterparts, and examine their function signature and usage.

1. useState: Managing State

In a class component, state is managed via the this.state object, and updates are made through this.setState(). With functional components and the useState hook, managing state becomes a lot more straightforward and eliminates the need for a constructor.

Class Component (Before Hooks)

import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 }; // Initializing state in the constructor
}
increment = () => {
this.setState({ count: this.state.count + 1 }); // Updating state with setState
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}

Functional Component (With useState)

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0); // Declaring state with useState hook
const increment = () => {
setCount(count + 1); // Updating state directly using setCount
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

Function Signature of useState:

const [state, setState] = useState(initialState);

Explanation:

  • useState(0) initializes the state variable count with an initial value of 0 and provides a setter function setCount to update the state.
  • In class components, state initialization and updates require more boilerplate code (constructor and this.setState()), making functional components with hooks much simpler and cleaner.

2. useEffect: Handling Side Effects

In class components, side effects like data fetching, subscriptions, or manually interacting with the DOM are handled with lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. With the useEffect hook, we can handle side effects in functional components more effectively.

Class Component (Before Hooks)

import React, { Component } from 'react';
class DataFetcher extends Component {
constructor(props) {
super(props);
this.state = { data: null };
}
componentDidMount() {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div>
{this.state.data ? <p>{this.state.data}</p> : <p>Loading...</p>}
</div>
);
}
}

Functional Component (With useEffect)

import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []); // Empty dependency array means this runs once after the initial render
return (
<div>
{data ? <p>{data}</p> : <p>Loading...</p>}
</div>
);
}

Function Signature of useEffect:

useEffect(() => {
// Code to run on mount and whenever dependencies change
}, [dependencies]); // Empty array for only on mount, or specific dependencies

Explanation:

  • useEffect replaces lifecycle methods like componentDidMount and componentDidUpdate.
  • The empty dependency array ([]) ensures the effect only runs once after the initial render, mimicking componentDidMount.
  • This makes functional components much easier to work with when dealing with side effects compared to class components.

3. useContext: Consuming Context

React’s context API allows components to access shared state without prop drilling. With the useContext hook, functional components can directly consume context values.

Class Component (Before Hooks)

import React, { Component } from 'react';
const ThemeContext = React.createContext('light');
class ThemedComponent extends Component {
static contextType = ThemeContext; // Access context via contextType in class component
render() {
return <p>The current theme is {this.context}</p>;
}
}

Functional Component (With useContext)

import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function ThemedComponent() {
const theme = useContext(ThemeContext); // Access context directly with useContext
return <p>The current theme is {theme}</p>;
}

Function Signature of useContext:

const value = useContext(MyContext);

Explanation:

  • Class Components: You use static contextType = MyContext to assign a context to the class, and then access it using this.context.
  • Functional Components: You can access the context directly with useContext(MyContext).

The useContext hook is much more intuitive in functional components than the class component approach.

4. useRef: Accessing DOM Elements

In class components, refs are created using React.createRef() and are often used to access DOM elements directly. The useRef hook simplifies this in functional components.

Class Component (Before Hooks)

import React, { Component } from 'react';

class FocusInput extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
focusInput = () => {
this.inputRef.current.focus(); // Direct DOM manipulation
};
render() {
return (
<div>
<input ref={this.inputRef} type="text" />
<button onClick={this.focusInput}>Focus the input</button>
</div>
);
}
}

Functional Component (With useRef)

import React, { useRef } from 'react';

function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // Direct DOM manipulation
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus the input</button>
</div>
);
}

Function Signature of useRef:

const ref = useRef(initialValue);

Explanation:

  • Class Components: You use React.createRef() in the constructor to create a reference to the DOM element.
  • Functional Components: The useRef hook simplifies it by providing a ref object that persists between renders.

Conclusion: Why Switch to Hooks?

React hooks offer a more declarative and concise way of writing components. They reduce the boilerplate code required in class components, making it easier to manage state, side effects, context, and refs. Here’s a quick recap of the benefits:

  • Simplicity: Functional components with hooks are easier to read and write.
  • Direct Access: Hooks like useState, useEffect, and useContext provide direct access to React features without needing complex lifecycle methods or this.
  • Reusability: With hooks, logic can be reused more effectively through custom hooks.

If you’re still working with class components, consider refactoring some of your code to functional components and hooks. It’ll not only improve the readability of your codebase but also make your development process more efficient in the long run. Happy coding!

--

--

Ly Channa
Ly Channa

Written by Ly Channa

Highly skilled: REST API, OAuth2, OpenIDConnect, SSO, TDD, RubyOnRails, CI/CD, Infrastruct as Code, AWS.

No responses yet