Converting class components to functional components (basic structure, state, & life-cycle methods)
Tags: react, lifecycle, state, class
Published: 2020-11-11 23:34:18

I encounter many developers who have learned the class components the first time they were learning React or have been using the class components for a long time, asking questions about converting the class components to functional components.

In this tutorial, I will be going over the conversion of an existing React project's basic structure, state, and life-cycle methods into functional components and hooks. If you started using React with the class components and are not comfortable with this transformation, this tutorial is just for you.

For sake of organization, here are the topics I will be going over.

## Table of Contents

### Basic Structure

- [Binding class methods](#binding)
  
- [`this` keyword](#this)
  
- [`render()` method](#render)
  
- [`props` object](#props)
  

### State

- [Creating state](#creating-state)
  
- [Updating state](#updating-state)
  
- [Consuming state](#consuming-state)
  

### Life-cycle methods

- [componentDidMount](#did-mount)
  
- [componentDidUpdate](#did-update)
  
- [componentWillUnmount](#will-unmount)
  

## TL;DR

| Class Components | Functional Components |
| --- | --- |
| Method binding required unless arrow functions used | No binding required |
| Use of `this` keyword | No `this` keyword |
| `render()` method | No `render()` method |
| `props` in constructor | `props` as functional component's parameter |
| Define `state` object in the beginning | Use `useState` hook |
| `state` can only be an object | `state` can be object, array, integer, string, etc. |
| Only one state object | Multiple `state` pieces |
| `this.setState` merges state objects | Setter methods replace state value |
| `this.setState` accepts an optional callback function as second argument | It is not the case |
| 3 most important life-cycle methods as separate functions | `useEffect` can *imitate* all three at once. |
| `componentDidUpdate` does not execute in the initial render | `useEffect` with non-empty dependency DOES also executes in the initial render |
| Have to manually check changes in `props` or `state` object in `componentDidUpdate` method | Dependency array as second parameter of `useEffect`takes care of it automatically |

## Basic Structure

Even though the structure of the class and functional components look different at first glance, most stuff in class components are omitted or overly simplified in functional components.

### Binding class methods <a name="binding"></a>

When we create a method in a class component, we have to bind it to this object (unless you create your method as an arrow function) so that we can use it in our component.

```javascript
class MyComponent extends React.Component {
    constructor() {
        super();
        this.myMethod = this.myMethod.bind(this);
    }
    myMethod() { 
        // do stuff
    }
    render() {
        return // some jsx
    }
}
```

In a functional component, no binding is necessary because there is no class. You may create your methods inside your component's function definition as you like (function definition, assignment to a variable, etc.)

```javascript
const MyComponent = () => {
    const myMethod = () => {
        // do stuff
    }
    return // some jsx
}
```

### `this` keyword <a name="this"></a>

In a functional component, we no longer need the `this` keyword. There is no class instance, so we do not refer to our `state`, `props`, or methods as a member of the class. Let's continue from the previous example. If we are to refer to the `myMethod` function in our JSX, we would do it like this:

```javascript
<button onClick={myMethod}>My Button</button>
```

### `render()` method <a name="render"></a>

In a functional component, we also do not need the `render()` method anymore. Whatever our functional component returns become our component's JSX.

### `props` object <a name="props"></a>

This is an obvious one because you probably used stateless functional components before, but I did not want to skip it.

In class components, you pass `props` to the base constructor so that you have access to the `props` object as `this.props`.

```javascript
class MyComponent extends React.Component {
    constructor(props) {
        super(props);
    }
    // do stuff
}
```

In a functional component, `props` comes as a parameter to the component's function definition.

```javascript
function MyComponent(props) {
    // do stuff
}
```

## State

Dealing with `state` in class and functional components are not too different. The most important part is probably understanding the difference between the `setState` method in class components and setter methods in functional components.

### Creating state <a name="creating-state"></a>

In older versions of React, the state used to be defined in the constructor. Later on, it changed so that you can define a state object right at the beginning of your component.

In older versions:

```javascript
class MyComponent extends React.Component {
    constructor() {
        this.state = { myState: "my value" }
    }
    // do stuff
}
```

Newer versions:

```javascript
class MyComponent extends React.Component {
    state = { myState: "my value" }
    // do stuff
}
```

In functional components, you need to use the `useState` hook for creating a new state piece. Also, in the class components, `state` has to be an object and there can be only one state object in a class component. This is not the case when creating a state with the `useState` hook.

```javascript
const MyComponent = () => {
    const [myState, setMyState] = useState('my value');
    const [myNumber, setMyNumber] = useState(22);
    const [myBool, setMyBool] = useState(false);

    // do stuff
}
```

Here, we created 3 different pieces of `state` for one component. One is a string, one is an integer, and one is a boolean value.

Let's explain the way we create a `state` here.

`useState` hook returns a tuple with two elements: the first one is the value of the `state` we created, the second one is a function for updating that specific piece of state, which brings me to the next topic.

### Updating state <a name="updating-state"></a>

When we are updating our state in class components, we utilize React's `setState` function which has a slightly different API compared to the setter method returned from the `useState` hook.

```javascript
class MyComponent extends React.Component {
    state = { 
        myState: "my value", 
        myOtherState: "my other value" 
    }

    updateMyState = () => {
        this.setState({ myState: "my newer value" });
    }

    render() {
        // After running this.updateMyState()
        console.log(this.state); // { myState: "my newer value", myOtherState: "my other value"}
        return // some JSX
    }

}
```

We pass an object to the `this.setState` method with the keys that we desire to update. `this.setState` automatically merges the passed state into the existing state. This is not the case when we are dealing with `state` as objects in functional components.

```javascript
const MyComponent = () => {
    const [myState, setMyState] = useState({ 
        myState: "my value", 
        myOtherState: "my other value" 
    });

    const updateMyState = () => {
        setMyState({ myState: "my newer value" });
    }

    // After running updateMyState()
    console.log(myState); // { myState: "my newer value" }

    return // some JSX
}
```

Another difference is that the second argument of `setState` accepts an optional callback function in class components to run after the state change happens. Even though the React documentation does not recommend using this method and instead recommends using the `componentDidUpdate` life-cycle method, you might be inclined to think that the setter method returned from `useState` in functional components would provide the same optional callback feature. But it does not.

### Consuming state <a name="consuming-state"></a>

This is a fairly easy one. Referring to a piece of state in a class component: `this.state.myState`.

In a functional component, whatever name you gave your state while de-structuring from the `useState` hook, that's your `state` name.

## Life-cycle Methods

Life-cycle methods might look a little bit trickier compared to what I've explained so far. We use the `useEffect` hook for *imitating* all three life-cycle methods I will be discussing here.

### componentDidMount <a name="did-mount"></a>

We use this life-cycle method for the side effects of our component, such as calling an API, etc. when the component is initially rendered. Everything inside this method is called once the initial rendering of the component is completed.

```javascript
class MyComponent extends React.Component {
    // state, etc.

    componentDidMount() {
        this.fetchSomeData();
    }

    // do stuff
}
```

To do the same thing in a functional component, we make use of our `useEffect` hook. `useEffect` takes two parameters: the first one is a function to call, the second one is an optional dependency array.

```javascript
const MyComponent = () => {
    // state, etc.

    useEffect(() => {
        fetchSomeData();
    }, []);


    // do stuff
}
```

When imitating `componentDidMount`, we leave the second dependency array empty. Why? Because React looks at that array and executes the function in `useEffect` if any value in that array changes. Since we only want to fetch our data once the component is initially rendered, we leave that array empty. An empty array means, "Hey React, watch this empty array. If anything changes, execute the function I gave you."

Here is an important note: whether we leave the dependency array empty, pass values in it, or don't even pass the array itself to `useEffect`; either way React executes the function in `useEffect` in the initial rendering, which brings me to the next life-cycle method.

### componentDidUpdate (prevProps, prevState) <a name="did-update"></a>

This life-cycle method is invoked after an update in `props` or `state` object occurs. It takes two parameters `prevProps` and `prevState` so we can check if the current `props` or `state` has changed in the last component update.

```javascript
class MyComponent extends React.Component {
    // state, props, etc.

    componentDidUpdate(prevProps) {
        if (this.props.id !== prevProps.id) {
            this.fetchData(this.props.id);
        }
    }

    // do stuff

}
```

Here we are checking if `this.props.id` has changed or not. If changed, we are fetching new data based on the new id. `useEffect` saves us some time when checking if the `props` object has changed or not.

```javascript
const MyComponent = (props) => {
    // state, etc.

    useEffect(() => {
        fetchData(props.id);
    }, [props.id]);

    // do stuff
}
```

We made use of the dependency array I was talking about earlier. Now React will watch props.id value and execute the function if it changes. I want to remind again: The function in `useEffect` will be executed in the initial render as well as following updates on `props.id` while `componentDidUpdate` will not be executed in the initial render.

If you remove the dependency array completely, the function in `useEffect` will run in every update of the component.

### componentWillUnmount <a name="will-unmount"></a>

This life-cycle method is invoked right before the component is unmounted. If you have ongoing side-effects that you started earlier such as a network request or a timer, this is the place you clean them up.

```javascript
class MyComponent extends React.Component {
    state = { counter: 0 }

    componentDidMount() {
        this.myTimer = setInterval(() => {
            this.setState({ counter: this.state.counter + 1 })
        }, 1000);
    }

    componentWillUnmount() {
        clearInterval(this.myTimer);
    }

    // do stuff
}
```

Here we created a timer in our `componentDidMount` life-cycle method. It updates and increases `this.state.counter` every second. If we do not clear this up in the `componentWillUnmount` life-cycle method, we will get `Can't perform a React state update on an unmounted component` error after the component is unmounted.

To do the same thing in functional components, we make use of the `return` keyword inside our function in `useEffect`. Let's create the same thing in a functional component.

```javascript
const MyComponent = (props) => {
    const [counter, setCounter] = useState(0);

    useEffect(() => {
        const myTimer = setInterval(() => {
            setCounter(counter => counter + 1);
        }, 1000);

        return () => {
            clearInterval(myTimer);
        }

    }, []);

    // do stuff
}
```

In case you haven't realized already, we did *imitate* `componentDidMount` and `componentWillUnmount` under one `useEffect` call.

Another note: Here we passed a function into `setCounter` method: `setCounter(counter => counter + 1)`. This is to avoid **stale closures**. Dmitri Pavlutin explain what a stale closure is **[here](https://dmitripavlutin.com/react-hooks-stale-closures/)** very well in case you haven't heard of it.

## Final Thoughts

Converting an existing React project from class components to functional components might look cumbersome.

When hooks were first introduced, the React team suggested a [Gradual Adoption Strategy](https://reactjs.org/docs/hooks-intro.html#gradual-adoption-strategy). However, it has been almost 2 years since and there is really not much that you can do in class components but not in functional components, thanks to hooks.

Furthermore, most libraries are adopting hooks by providing new API designed with them. Many React developers find hooks a clear, concise way of building apps with React. If you have never used functional components with hooks before, it is my personal opinion that it is time to start considering.