Converting React Class Components to Function Components using Hooks
Tags: javascript, react, reacthooks, functioncomponents
Published: 2019-03-11 12:00:35

---
title: Converting React Class Components to Function Components using Hooks
description: With React Hooks now being part of the stable release, let's take a look at how to convert a class-based React component to a Function component, including state usage and life cycle methods.
tags: javascript, react, react-hooks, function-components
published: true
canonical_url: https://asleepysamurai.com/articles/converting-react-class-components-to-function-components-using-hooks?ref=dev.to
cover_image: https://asleepysamurai.com/articles/converting-react-class-components-to-function-components-using-hooks/img/hooks.jpg
---

Over the course of the past month, I've spent a lot of time working with React hooks, and building function components using hooks, instead of the traditional class-based components. I'm not going to claim that hooks are definitely superior to class-based components, but there is a huge usability boost, while writing your components as function components, especially since these function components can now access state and lifecycle through the use of React hooks.

In this article, I'm going to be showing you how to convert a class-based React component to a function component, replacing the class based `setState` and lifecycle methods such as `componentWillMount`, `componentWillReceiveProps` with React hooks instead.

So let's first build a class-based React component, which makes use of state as well as lifecycle methods. For this, we shall of course build the traditional React Todo Application.

Our Todo app is going to look like this:

- A text input, into which you can type new todo items.
- An 'Add Todo' button, clicking which the new todo item in the text input, is added to the todo list.
- A list displaying each individual todo item.
- Each individual todo item has an associated checkbox which can be used to mark the item as completed.
- The todos are persisted to local storage, and loaded again from local storage when the app is initiated.

Our components will use `state`, `componentDidMount`, `componentDidUpdate` and `getDerivedStateFromProps` lifecycle methods. A few of these methods (I'm looking at you `getDerivedStateFromProps`) are used in an extremely contrived manner, in order to be able to demonstrate how a hooks based replacement would look like.

<hr />

##### Building the Class-Based Todo App

Our todo app will be implemented as three different components, like this:

![App Structure](https://asleepysamurai.com/articles/converting-react-class-components-to-function-components-using-hooks/img/components.png)

> The complete code for these examples can be found on [Github](https://github.com/asleepysamurai/example-react-class-to-function-component). Use the tags to navigate between class components and function components.

Let's take a look at the `TodoContainer` component first. This component maintains the complete state of the application, at any time in it's `state`. It has two methods `addTodoItem` and `toggleItemCompleted` which are passed down as callbacks to the `TodoInput` and `TodoItem` components respectively, and are used to add a new todo item, and mark an item as completed or incomplete.

In addition, the `TodoContainer` component makes use of the `componentDidMount` lifecycle method to load saved todo items from the `localStorage`.  If there are no saved todo items, then an empty list is instantiated as the state of the `TodoContainer` component. `TodoContainer` also uses the `componentDidUpdate` lifecycle method to save the state of the `TodoContainer` component, to the `localStorage`. This way, whenever there is a change to the `state` of `TodoContainer` it is persisted to `localStorage` and can be restored upon relaunching the app.

Putting all of that together, `TodoContainer` component looks like this:

```js
/**
 * Todo Root Container Component
 */

import React, { Component } from 'react';
import TodoItem from './TodoItem';
import TodoInput from './TodoInput';

class Todo extends Component {
    constructor(props) {
        super(props);

        this.state = {
            todoItems: [],
            completedItemIds: []
        };
    }

    generateID() {
        return Date.now().toString(36) + '-' + (Math.random() + 1).toString(36).substring(7);
    }

    addTodoItem = (text) => {
        const newTodoItem = {
            text,
            id: this.generateID()
        };

        const todoItems = this.state.todoItems.concat([newTodoItem]);
        this.setState({ todoItems });
    }

    toggleItemCompleted = (todoItemId) => {
        const todoItemIndexInCompletedItemIds = this.state.completedItemIds.indexOf(todoItemId);

        const completedItemIds = todoItemIndexInCompletedItemIds === -1 ?
            this.state.completedItemIds.concat([todoItemId]) :
            ([
                ...this.state.completedItemIds.slice(0, todoItemIndexInCompletedItemIds),
                ...this.state.completedItemIds.slice(todoItemIndexInCompletedItemIds + 1)
            ]);

        this.setState({ completedItemIds });
    }

    componentDidMount() {
        let savedTodos = localStorage.getItem('todos');

        try {
            savedTodos = JSON.parse(savedTodos);
            this.setState(Object.assign({}, this.state, savedTodos));
        } catch (err) {
            console.log('Saved todos non-existent or corrupt. Trashing saved todos.');
        }
    }

    componentDidUpdate() {
        localStorage.setItem('todos', JSON.stringify(this.state));
    }

    render() {
        const todoList = this.state.todoItems.map(todoItem => {
            return (
                <TodoItem
                    key={todoItem.id}
                    completedItemIds={this.state.completedItemIds}
                    toggleItemCompleted={this.toggleItemCompleted}
                    {...todoItem} />
            );
        });

        const todoInput = (
            <TodoInput
                onAdd={this.addTodoItem} />
        );

        return (
            <div
                className="todo-container">
                {todoList}
                {todoInput}
            </div>
        );
    }
};

export default Todo;
```

Next let's look at the `TodoInput` component. This is a very simple component, which consists of a single text input and a button. The component uses it's own `state` to keep track of the value of the text input, and upon button click, passes that text along to the `addTodoItem` method passed in as the `onAdd` prop by the `TodoContainer` component.

The `TodoInput` component looks like this:

```js
/**
 * TodoInput Component
 */

import React, { Component } from 'react';

class TodoInput extends Component {
    constructor(props) {
        super(props);

        this.state = {
            text: ''
        };
    }

    onTextChange = (ev) => {
        this.setState({ text: ev.currentTarget.value });
    }

    addTodoItem = () => {
        this.props.onAdd(this.state.text);
        this.setState({ text: '' });
    }

    render() {
        return (
            <div
                className="todo-input">
                <input
                    type="text"
                    onChange={this.onTextChange}
                    value={this.state.text}
                    placeholder="Enter Todo Here" />
                <button
                    onClick={this.addTodoItem}>
                    Add Todo
                </button>
            </div>
        );
    }
};

export default TodoInput;
```

Finally, the `TodoItem` component. This is another simple component, which basically consists of a checkbox, indicating whether the todo item is completed, and a label for that checkbox, specifying the text of the todo item. In order to demonstrate the use of `getDerivedStateFromProps`, the `TodoItem` component takes in the entire `completedItemIds` from the `TodoContainer` component as a prop, and uses it to calculate if this particular `TodoItem` is complete or not.

The `TodoItem` component looks like this:

```js
/**
 * TodoItem Component
 */

import React, { Component } from 'react';

class TodoItem extends Component {
    constructor(props) {
        super(props);

        this.state = {
            completed: false
        };
    }

    toggleItemCompleted = () => {
        this.props.toggleItemCompleted(this.props.id);
    }

    static getDerivedStateFromProps(props, state) {
        const todoItemIndexInCompletedItemIds = props.completedItemIds.indexOf(props.id);

        return { completed: todoItemIndexInCompletedItemIds > -1 };
    }

    render() {
        return (
            <div
                className="todo-item">
                <input
                    id={`completed-${this.props.id}`}
                    type="checkbox"
                    onChange={this.toggleItemCompleted}
                    checked={this.state.completed} />
                <label>{this.props.text}</label>
            </div>
        );
    }
};

export default TodoItem;
```

<hr />

##### Rules to Remember While Getting Hooked

In order to demonstrate the various React hooks we are going to be using to convert our Todo app to use React function components, I'm going to be starting with the simple hooks, and moving on to the more complicated ones. But before we do that, let's take a quick look at the most important rules to keep in mind while using hooks.

- Hooks should only be called from within a React function component, or another hook.
- The same hooks should be called in the same order, the same number of times, during the render of a single component. This means that hooks cannot be called within loop or conditional blocks, and must instead always be called at the top level of the function.

The reasoning behind these rules for using hooks are a topic that can be an article by itself, and if you are interested in reading more about this, you should check out the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) article on the official React documentation site.

Now, that we know the basic rules to be followed while using hooks, let's go ahead and start converting our Todo app to use function components.

<hr />

##### Function Components for our Todo App

The simplest, and probably the one hook that you are going to end up using the most is the `useState` hook. The `useState` hook basically provides you with a setter and a getter to manipulate a single state property on the component. The `useState` hook has the following signature:

```js
const [value, setValue] = useState(initialValue);
```

The first time the hook is called, the state item is initialized with the `initialValue`. Upon subsequent calls to `useState`, the previously set value will be returned, along with a setter method, which can be used to set a new value for that particular state property.

So let's convert the `TodoInput` component to a function component, using the `useState` hook.

```js
/**
 * TodoInput Component
 */

import React, { useState } from 'react';

function TodoInput({
    onAdd
}) {
    const [text, setText] = useState('');

    const onTextChange = (ev) => setText(ev.currentTarget.value);

    const addTodoItem = () => {
        onAdd(text);
        setText('');
    };

    return (
        <div
            className="todo-input">
            <input
                type="text"
                onChange={onTextChange}
                value={text}
                placeholder="Enter Todo Here" />
            <button
                onClick={addTodoItem}>
                Add Todo
            </button>
        </div>
    );
};

export default TodoInput;
```

As you can see, we are using `useState` to get the `text` state property value and `setText` setter, obtained from the `useState` method. The `onTextChange` and `addTodoItem` methods have both been changed to use the `setText` setter method, instead of `setState`.

<hr />

You might have noticed that the `onChange` event handler for the `input`, as well as the `Add Todo` button `onClick` event handlers are both anonymous functions, created during the render, and as you might know, this is not really good for performance, since the reference to the functions changes between renders, thus forcing React to re-render both the `input` and the `button`.

In order to avoid these unnecessary re-renders, we need to keep the reference to these functions the same. This is where the next hook we are going to use `useCallback` comes into the picture. `useCallback` has the following signature:

```js
const memoizedFunction = useCallback(inlineFunctionDefinition, memoizationArguments);
```

where:
- `inlineFunctionDefinition` - is the function that you wish to maintain the reference to between renders. This can be an inline anonymous function, or a function that get's imported from somewhere else. However, since in most cases we would want to refer to state variables of the component, we will be defining this as an inline function, which will allow us to access the state variables using closures.
- `memoizationArguments` - is an array of arguments that the `inlineFunctionDefinition` function references. The first time the `useCallback` hook is called, the `memoizationArguments` are saved along with the `inlineFunctionDefinition`. Upon subsequent calls, each element in the new `memoizationArguments` array is compared to the value of the element at the same index in the previously saved `memoizationArguments` array, and if there is no change, then the previously saved `inlineFunctionDefinition` is returned, thus preserving the reference, and preventing an unnecessary re-render. If any of the parameters have changed, then the `inlineFunctionDefinition` and the new `memoizationArguments` are saved and used, thus changing the reference to the function, and ensuring a re-render.

So adapting `TodoInput` to use `useCallback`:

```js
/**
 * TodoInput Component
 */

import React, { useState, useCallback } from 'react';

function TodoInput({
    onAdd
}) {
    const [text, setText] = useState('');

    const onTextChange = useCallback((ev) => setText(ev.currentTarget.value), [setText]);

    const addTodoItem = useCallback(() => {
        onAdd(text);
        setText('');
    }, [onAdd, text, setText]);

    return (
        <div
            className="todo-input">
            <input
                type="text"
                onChange={onTextChange}
                value={text}
                placeholder="Enter Todo Here" />
            <button
                onClick={addTodoItem}>
                Add Todo
            </button>
        </div>
    );
};

export default TodoInput;
```

<hr />

Now that we have converted `TodoInput` to a function component, let's do the same with `TodoItem`. Rewriting using `useState` and `useCallback`, `TodoItem` becomes:

```js
/**
 * TodoItem Component
 */

import React, { useState, useCallback } from 'react';

function TodoItem({
    id,
    text,
    toggleItemCompleted,
    completedItemIds
}) {
    const [completed, setCompleted] = useState(false);

    const onToggle = useCallback(() => {
        toggleItemCompleted(id);
    }, [toggleItemCompleted, id]);

    return (
        <div
            className="todo-item">
            <input
                id={`completed-${id}`}
                type="checkbox"
                onChange={onToggle}
                checked={completed} />
            <label>{text}</label>
        </div>
    );
};

export default TodoItem;
```

If you compare this with the class component version of `TodoItem`, you'll notice that the `getDerivedStateFromProps` which we used to determine if this particular `TodoItem` was completed or not, is missing in the function component version. So which hook would we use to implement that?

Well there exists no specific hook to implement this. Instead, we will have to implement this as part of the render function itself. So once we implement it, `TodoItem` looks like this:

```js
/**
 * TodoItem Component
 */

import React, { useState, useCallback } from 'react';

function TodoItem({
    id,
    text,
    toggleItemCompleted,
    completedItemIds
}) {
    const [completed, setCompleted] = useState(false);

    const todoItemIndexInCompletedItemIds = completedItemIds.indexOf(id);
    const isCompleted = todoItemIndexInCompletedItemIds > -1;

    if (isCompleted !== completed) {
        setCompleted(isCompleted);
    }

    const onToggle = useCallback(() => {
        toggleItemCompleted(id);
    }, [toggleItemCompleted, id]);

    return (
        <div
            className="todo-item">
            <input
                id={`completed-${id}`}
                type="checkbox"
                onChange={onToggle}
                checked={completed} />
            <label>{text}</label>
        </div>
    );
};

export default TodoItem;
```

You might notice that we are calling the `setCompleted` state setter method during the rendering of the component. While writing class components, we never call `setState` in the `render` method, so why is this acceptible in function components?

This is allowed in function components specifically to allow us to perform `getDerivedStateFromProps`-esque actions. An important thing to keep in mind, is to ensure that, inside a function component, we always call state setter methods inside a conditional block. Otherwise, we are going to end up in an infinite loop.

> Please note, the way I've implemented the `isCompleted` check here is a bit contrived, in order to demonstrate setting the state from within the function component. Ideally, the `completed` state simply would not be used, and the calculated `isCompleted` value would be used to set the `checked` state of the checkbox.

<hr />

Finally, we've only got `TodoContainer` component to be converted. We'll need to implement `state` as well as the `componentDidMount` and `componentDidUpdate` lifecycle methods.

Since we've already seen about `useState` and I don't really have a good excuse to demonstrate `useReducer`, I'm going to pretend that `TodoContainer` component's state is too complicated to manage each state property individually using `useState`, and that the better option is to use `useReducer`.

The signature of the `useReducer` hook, looks like this:

```js
const [state, dispatch] = useReducer(reducerFunction, initialState, stateInitializerFunction);
```

where:

- `reducerFunction` - is the function that takes existing `state` and an `action` as an input and returns a new `state` as the output. This should be familiar to anybody who has used Redux and it's reducers.
- `initialState` - If there is no `stateInitializerFunction` provided, then this is the initial `state` object for the component. If a `stateInitializerFunction` is provided, this is passed in as an argument to that function.
- `stateInitializerFunction` - A function which allows you to perform lazy initialization of the component's state. The `initialState` parameter will be passed in as an argument to this function.

So converting `TodoContainer` component to use `useReducer`:

```js
/**
 * Todo Root Container Component
 */

import React, { useReducer, useCallback } from 'react';
import TodoItem from './TodoItem';
import TodoInput from './TodoInput';

const generateID = () => {
    return Date.now().toString(36) + '-' + (Math.random() + 1).toString(36).substring(7);
};

const reducer = (state, action) => {
    if (action.type === 'toggleItemCompleted') {
        const { todoItemId } = action;
        const todoItemIndexInCompletedItemIds = state.completedItemIds.indexOf(todoItemId);

        const completedItemIds = todoItemIndexInCompletedItemIds === -1 ?
            state.completedItemIds.concat([todoItemId]) :
            ([
                ...state.completedItemIds.slice(0, todoItemIndexInCompletedItemIds),
                ...state.completedItemIds.slice(todoItemIndexInCompletedItemIds + 1)
            ]);

        return { ...state, completedItemIds };
    }

    if (action.type === 'addTodoItem') {
        const newTodoItem = {
            text: action.text,
            id: generateID()
        };

        const todoItems = state.todoItems.concat([newTodoItem]);
        return { ...state, todoItems };
    }

    return state;
};

const initialState = {
    todoItems: [],
    completedItemIds: []
};

function Todo() {
    const [state, dispatch] = useReducer(reducer, initialState);

    const toggleItemCompleted = useCallback((todoItemId) => {
        dispatch({ type: 'toggleItemCompleted', todoItemId });
    }, [dispatch]);

    const todoList = state.todoItems.map(todoItem => {
        return (
            <TodoItem
                key={todoItem.id}
                completedItemIds={state.completedItemIds}
                toggleItemCompleted={toggleItemCompleted}
                {...todoItem} />
        );
    });

    const addTodoItem = useCallback((text) => {
        dispatch({ type: 'addTodoItem', text });
    }, [dispatch]);

    const todoInput = (
        <TodoInput
            onAdd={addTodoItem} />
    );

    return (
        <div
            className="todo-container">
            {todoList}
            {todoInput}
        </div>
    );
};

export default Todo;
```

<hr />

Next we need to write the state of the `TodoContainer` component to `localStorage`, whenever the component is updated, similar to what we were doing in `componentDidUpdate`. To do this, we will make use of the `useEffect` hook. The `useEffect` hook allows us to enqueue a certain action to be done, after each render of the component is completed. It's signature is:

```js
useEffect(enqueuedActionFunction);
```

As long as you adhere to the rules of hooks, we talked about earlier, you can insert a `useEffect` block anywhere in your function component. If you have multiple `useEffect` blocks, they will be executed in sequence. The general idea of `useEffect` is to perform any actions that do not directly impact the component in the `useEffect` block (ex: API calls, DOM manipulations etc.).

We can use `useEffect` to ensure that after every single render of our `TodoContainer`, the state of the component is written to `localStorage`.

```js
/**
 * Todo Root Container Component
 */

import React, { useReducer, useCallback, useEffect } from 'react';
import TodoItem from './TodoItem';
import TodoInput from './TodoInput';

const generateID = () => {
    return Date.now().toString(36) + '-' + (Math.random() + 1).toString(36).substring(7);
};

const reducer = (state, action) => {
    if (action.type === 'toggleItemCompleted') {
        const { todoItemId } = action;
        const todoItemIndexInCompletedItemIds = state.completedItemIds.indexOf(todoItemId);

        const completedItemIds = todoItemIndexInCompletedItemIds === -1 ?
            state.completedItemIds.concat([todoItemId]) :
            ([
                ...state.completedItemIds.slice(0, todoItemIndexInCompletedItemIds),
                ...state.completedItemIds.slice(todoItemIndexInCompletedItemIds + 1)
            ]);

        return { ...state, completedItemIds };
    }

    if (action.type === 'addTodoItem') {
        const newTodoItem = {
            text: action.text,
            id: generateID()
        };

        const todoItems = state.todoItems.concat([newTodoItem]);
        return { ...state, todoItems };
    }

    return state;
};

const initialState = {
    todoItems: [],
    completedItemIds: []
};

function Todo() {
    const [state, dispatch] = useReducer(reducer, initialState);

    useEffect(() => {
        localStorage.setItem('todos', JSON.stringify(state));
    });

    const toggleItemCompleted = useCallback((todoItemId) => {
        dispatch({ type: 'toggleItemCompleted', todoItemId });
    }, [dispatch]);

    const todoList = state.todoItems.map(todoItem => {
        return (
            <TodoItem
                key={todoItem.id}
                completedItemIds={state.completedItemIds}
                toggleItemCompleted={toggleItemCompleted}
                {...todoItem} />
        );
    });

    const addTodoItem = useCallback((text) => {
        dispatch({ type: 'addTodoItem', text });
    }, [dispatch]);

    const todoInput = (
        <TodoInput
            onAdd={addTodoItem} />
    );

    return (
        <div
            className="todo-container">
            {todoList}
            {todoInput}
        </div>
    );
};

export default Todo;
```

<hr />

Next we need to restore the state from the `localStorage` whenever the component is mounted, similar to what we do in `componentDidMount`. Now there are no specific hooks to perform this, but we can use the `useReducer` hooks lazy initialization function (which will be called only on the first render), to achieve this.

```js
/**
 * Todo Root Container Component
 */

import React, { useReducer, useCallback, useEffect } from 'react';
import TodoItem from './TodoItem';
import TodoInput from './TodoInput';

const generateID = () => {
    return Date.now().toString(36) + '-' + (Math.random() + 1).toString(36).substring(7);
};

const reducer = (state, action) => {
    if (action.type === 'toggleItemCompleted') {
        const { todoItemId } = action;
        const todoItemIndexInCompletedItemIds = state.completedItemIds.indexOf(todoItemId);

        const completedItemIds = todoItemIndexInCompletedItemIds === -1 ?
            state.completedItemIds.concat([todoItemId]) :
            ([
                ...state.completedItemIds.slice(0, todoItemIndexInCompletedItemIds),
                ...state.completedItemIds.slice(todoItemIndexInCompletedItemIds + 1)
            ]);

        return { ...state, completedItemIds };
    }

    if (action.type === 'addTodoItem') {
        const newTodoItem = {
            text: action.text,
            id: generateID()
        };

        const todoItems = state.todoItems.concat([newTodoItem]);
        return { ...state, todoItems };
    }

    return state;
};

const initialState = {
    todoItems: [],
    completedItemIds: []
};

const initState = (state) => {
    let savedTodos = localStorage.getItem('todos');

    try {
        savedTodos = JSON.parse(savedTodos);
        return Object.assign({}, state, savedTodos);
    } catch (err) {
        console.log('Saved todos non-existent or corrupt. Trashing saved todos.');
        return state;
    }
};

function Todo() {
    const [state, dispatch] = useReducer(reducer, initialState, initState);

    useEffect(() => {
        localStorage.setItem('todos', JSON.stringify(state));
    });

    const toggleItemCompleted = useCallback((todoItemId) => {
        dispatch({ type: 'toggleItemCompleted', todoItemId });
    }, [dispatch]);

    const todoList = state.todoItems.map(todoItem => {
        return (
            <TodoItem
                key={todoItem.id}
                completedItemIds={state.completedItemIds}
                toggleItemCompleted={toggleItemCompleted}
                {...todoItem} />
        );
    });

    const addTodoItem = useCallback((text) => {
        dispatch({ type: 'addTodoItem', text });
    }, [dispatch]);

    const todoInput = (
        <TodoInput
            onAdd={addTodoItem} />
    );

    return (
        <div
            className="todo-container">
            {todoList}
            {todoInput}
        </div>
    );
};

export default Todo;
```

As you can see, we read the saved previous state from `localStorage`, and use that to initilize the `state` of the `TodoContainer` component, thus mimicking what we were doing in `componentDidMount`. And with that, we have converted our entire todo app, to function components.

<hr />

##### In conclusion...

In this article, I've only covered a few of the hooks that are available out of the box in React. Apart from these out of the box hooks, we can also write our own custom hooks to perform more specific tasks.

One of the key takeaways from this little exercise in converting class components to function components, is that there is no one to one mapping between lifecycle methods and React hooks. While `useState` and `useReducer` usage is quite similar to `setState`, you might have to change the way you are doing certain tasks in order to get them working in a function component (like `getDerivedStateFromProps` in our Todo app).

As such converting a React class component to a function component is something that may be trivial, or quite complicated depending on the component. Unless you're writing a new component, there really is no hard need to convert a class component to a function component, especially since React is going to continue supporting both forms for the foreseeable future.

That said, if you do find yourself needing to convert an existing class component to a function component, I hope this article provided you with a good starting point. I'd love to hear your opinion on things I've written in this article, so do chime in below.

<hr />

This article was originally published at [asleepysamurai.com](https://asleepysamurai.com/articles/converting-react-class-components-to-function-components-using-hooks?ref=dev.to)