blog post

Migrating from Class Components to Functional Components in React

React has evolved significantly over the years, and one of the major changes in version 16.8.0 is the introduction of hooks, which allow you to use state and other React features without writing a class. In this guide, we will walk through migrating various types of class components to functional components.

Functional components existed before but with an introduction of hooks - they look absolutely different.

What Are Class Components

Class components are ES6 JavaScript classes that extend from React.Component and implement a render method, which returns React elements (JSX). Here's a basic example:

jsx
class TextComponent extends React.Component { render() { return <h1>Hello, World!</h1>; } }

State in Class Components

Class components can have state, a built-in object that allows components to manage their internal data. State is local to the component and can change over time.

Let's create a Counter component that manages count variable in the state. The component initializes its state in the constructor and provides a method to update the state using this.setState:

jsx
import React from "react"; 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> ); } }

Props in Class Components

Props (short for properties) are used to pass data from parent components to child components. Props are read-only and cannot be modified by the child component unlike a state.

jsx
export class TextComponent extends React.Component { render() { return <h1>Hello, {this.props.name}!</h1>; } } function App() { return ( <div className="main"> <TextComponent name={"Anton"} /> </div> ) }

Lifecycle Methods in Class Components

Lifecycle methods are special methods that allow you to run code at specific points in a component's lifecycle, such as when a component is created, updated, or destroyed.

Common Lifecycle Methods:

  • componentDidMount: is called once, after the component is added to the DOM.
  • componentDidUpdate: is called each time after the component is updated when props or state is changed.
  • componentWillUnmount: is called once, before the component is removed from the DOM.

Let's explore a class component with state lifecycle methods. We'll create a component that fetches blog posts when it is mounted and renders them on the screen:

jsx
export class PostList extends React.Component { constructor(props) { super(props); this.state = { data: [] }; } componentDidMount() { console.log("Component is mounted"); fetch("https://jsonplaceholder.typicode.com/posts") .then(response => response.json()) .then(data => this.setState({ data })); } componentDidUpdate(prevProps, prevState) { if (prevState.data !== this.state.data) { console.log("Data updated:", this.state.data); } } componentWillUnmount() { console.log("Component will unmount"); } render() { if (!this.state.data) { return (<div><p>Loading...</p></div>); } return ( <div> {this.state.data.slice(0, 5).map((post) => <Post key={post.id} postData={post} /> )} </div> ); } }

In componentDidMount method we fetch a list of blog posts from the test URL and save them into component's state.

componentDidUpdate method is invoked after the component updates and needs to be re-rendered. Here it checks if the data state variable has changed. If true, it logs the new data. This method is called when component's state is populated with blog posts.

componentWillUnmount method is called just before a component is destroyed or removed from the DOM. It happens when a user navigates to a different page and the component disappeared from the screen.

Now let's explore how to migrate all mentioned class components into functional components with hooks.

Migrating Classes Without State and Lifecycle To Functional Components

Let's transform our first Hello World component into a functional one:

jsx
const TextComponent = () => { return <h1>Hello, World!</h1>; }

If the component has only a render logic and doesn't have any additional javascript code, you can omit the { } and return:

jsx
const TextComponent = () => ( <h1>Hello, World!</h1>; )

Migrating Classes With State To Functional Components

Now let's migrate the Counter component into a functional one:

jsx
const Counter = () => { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div className="flex"> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); };

Here we are using the useState hook that allows to save and update data inside a functional component.

The useState hook contains a variable itself and a function that allows to a change value of the variable. Everytime a setCount function is called - a component is rendered with a new value of count variable.

As you can see this functional approach with useState hook requires less code to write and is easy to get used to.

Migrating Classes With Props To Functional Components

Functional components receive props as the function argument and the TextComponent migration is straightforward:

jsx
const TextComponent = (props) => { return <h1>Hello, {props.name}!</h1>; } function App() { return ( <div className="main"> <TextComponent name={"Anton"} /> </div> ) }

Migrating Classes With Lifecycle Methods To Functional Components

Let's migrate the PostList component to the functional one:

jsx
import React, { useEffect, useState } from 'react'; export const PostList = () => { const [data, setData] = useState([]); useEffect(() => { console.log("Component is mounted"); fetch('https://jsonplaceholder.typicode.com/posts') .then((response) => response.json()) .then((data) => setData(data)); return () => { console.log("Component will unmount"); }; }, []); useEffect(() => { console.log("Data updated:", this.state.data); }, [data]); if (!data.length) { return ( <div> <p>Loading...</p> </div> ); } return ( <div> {data.slice(0, 5).map((post) => ( <Post key={post.id} postData={post} /> ))} </div> ); };

This functional approach also requires less code and doesn't require additional condition checks whether the data is updated. In class components when you have a lot of data in the state that is changing - the componentDidUpdate method can become a big ball of mud with a lot of conditional checks.

In functional component the useEffect hook is used that combines all three class lifecycle functions: componentDidMount, componentDidUpdate and componentWillUnmount.

useEffect hook has 2 parameters: a function that performs a side effect and an array of tracked variables. When a variable value changes - a useEffect hook is triggered.

The first hook has an empty braces [ ] without parameters, this effect function will be called only once after a component is loaded. It is an analog of componentDidMount class method.

Second useEffect hook is called only when data variable is changed. It is an analog of componentDidMount class method.

You can use as many useEffect hooks in the functional component as you want. And you can specify multiple parameters inside [ ] of the hook.

And what about componentWillUnmount analog? It is represented as a returned function inside a useEffect without parameters:

jsx
useEffect(() => { console.log("Component is mounted"); // ... return () => { console.log("Component will unmount"); }; }, []);

You can learn more about React hooks in my blog post

Summary

In this blog post we have talked about what React class and functional components are. Both class and functional components have state, props and lifecycle methods.

Components with hook offer a functional approach to writing components. Hooks are a modern way to write your components, and nowadays, most of the React applications are built with them.

So should you jump in and rewrite all class components to the hooks? Not exactly. I recommend writing all new components with hooks if you can update the React version to 16.8.0 in your project. And migrate class components to the functional ones as needed.

Hope you find this blog post useful. Happy coding!

Improve Your .NET and Architecture Skills

Join my community of 1800+ developers and architects.

Each week you will get 2 practical tips with best practises and architecture advice.