Functional Components vs. Class Components

In the world of React, there are two ways of writing a React component. One uses a function and the other uses a class.

Category Fundamentals | Tags: Functional, Components, Class

Published: 14 August 2021

Recently functional components are becoming more and more popular, so why is that?

This article will help you understand the differences between functional and class components by walking through each one with sample code so that you can dive into the world of modern React!

Rendering JSX

First of all, the clear difference is the syntax. Just like in their names, a functional component is just a plain JavaScript function that returns JSX. A class component is a JavaScript class that extends React.Component which has a render method. A bit confusing? Let’s take a look into a simple example.

import React from "react";

const FunctionalComponent = () => {
    return <h1>Hello, world</h1>;
};


As you can see, a functional component is a function that returns JSX. If you are not familiar with arrow functions introduced in ES6, you can also check out the example below without.

import React from "react";

function FunctionalComponent() {
    return <h1>Hello, world</h1>;
}


On the other hand, when defining a class component, you have to make a class that extends React.Component. The JSX to render will be returned inside the render method.

import React, { Component } from "react";

class ClassComponent extends Component {
    render() {
        return <h1>Hello, world</h1>;
    }
}


Below is the same example but without using destructuring. If you are not familiar with destructuring, you can learn more about destructuring and arrow functions introduced in ES6!

import React from "react";

class ClassComponent extends React.Component {
    render() {
        return <h1>Hello, world</h1>;
    }
}

Passing props

Passing props can be confusing, but let’s see how they are written in both class and functional components. Let’s say we are passing props of the name “Shiori” like below.

<Component name="Shiori" />;
const FunctionalComponent = ({ name }) => {
    return <h1>Hello, {name}</h1>;
};


Inside a functional component, we are passing props as an argument of the function. Note that we are using destructuring here. Alternatively, we can write it without as well.

const FunctionalComponent = (props=> {
    return <h1>Hello, {props.name}</h1>;
};


In this case, you have to use props.name instead of name.

class ClassComponent extends React.Component {
    render() {
        const { name } = this.props;
        return <h1>Hello, {name}</h1>;
    }
}


Since it is a class, you need to use this to refer to props. And of course, we can use destructuring to get name inside props while utilizing class-based components.

Handling state

Now we all know that we cannot avoid dealing with state variables in a React project. Handling state was only doable in a class component until recently, but from React 16.8, React Hook useState was introduced to allow developers to write stateful functional components. You can learn more about Hooks from the official documentation. Here we are going to make a simple counter that starts from 0, and one click on button will increment the count by 1.

Handling state in functional components

const FunctionalComponent = () => {
    const [count, setCount= React.useState(0);

    return (
        <div>
            <p>count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Click</button>
        </div>
    );
};


To use state variables in a functional component, we need to use useState Hook, which takes an argument of initial state. In this case we start with 0 clicks so the initial state of count will be 0.

Of course you can have more variety of initial state including nullstring, or even object - any type that JavaScript allows! And on the left side, as useState returns the current state and a function that updates it, we are destructuring the array like this. If you are a bit confused about the two elements of the array, you can consider them as a state and its setter. In this example we named them count and setCount to make it easy to understand the connection between the two.

Handling state in class components

class ClassComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
        };
    }

    render() {
        return (
            <div>
                <p>count: {this.state.count} times</p>
                <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click</button>
            </div>
        );
    }
}


The idea is still the same but a class component handles state a bit differently. Firstly, we need to understand the importance of the React.Component constructor. Here is the definition from the official documentation:

The constructor for a React component is called before it is mounted. When implementing the constructor for a React.Component subclass, you should call super(props) before any other statement. Otherwise, this.props will be undefined in the constructor, which can lead to bugs.

Basically, without implementing the constructor and calling super(props), all the state variables that you are trying to use will be undefined. So let’s define the constructor first. Inside the constructor, you will make a state object with a state key and initial value. And inside JSX, we use this.state.count to access the value of the state key we defined in the constructor to display the count. Setter is pretty much the same, just different syntax.

Alternatively, you can write an onClick function. Remember, the setState function takes argument(s) of state, props(optional) if needed.

onClick={() =>
    this.setState((state=> {
        return { count: state.count + 1 };
    })
}

Lifecycle Methods

Finally, let’s talk about lifecycles. Hang on, we are almost there! As you already know, lifecycles play an important role in the timing of rendering. For those of you who are migrating from class components to functional components, you must be wondering what could replace lifecycle methods such as componentDidMount() in a class component. And yes, there is a hook that works perfectly for the purpose, let’s check it out!

On Mounting (componentDidMount)

The lifecycle method componentDidMount is called right after the first render completes. There used to be a componentWillMount that happens before the first render, but it is considered legacy and not recommended to use in newer versions of React.

const FunctionalComponent = () => {
    React.useEffect(() => {
        console.log("Hello");
    }, []);
    return <h1>Hello, World</h1>;
};


Replacing componentDidMount, We use the useEffect hook with the second argument of []. The second argument of the useState hook is normally an array of a state(s) that changes, and useEffect will be only called on these selected changes. But when it’s an empty array like this example, it will be called once on mounting. This is a perfect replacement for a componentDidMount.

class ClassComponent extends React.Component {
    componentDidMount() {
        console.log("Hello");
    }

    render() {
        return <h1>Hello, World</h1>;
    }
}


Basically the same thing happens here: componentDidMount is a lifecycle method that is called once after the first render.

On Unmounting (componentWillUnmount)

const FunctionalComponent = () => {
    React.useEffect(() => {
        return () => {
            console.log("Bye");
        };
    }, []);
    return <h1>Bye, World</h1>;
};


I am happy to tell you that we can also use a useState hook for unmounting as well. But be careful, the syntax is a bit different. What you need to do is return a function that runs on unmounting inside the useEffect function. This is especially useful when you have to clean up the subscriptions such as a clearInterval function, otherwise it can cause a severe memory leak on a bigger project. One advantage of using useEffect is that we can write functions for both mounting and unmounting in the same place.

class ClassComponent extends React.Component {
    componentWillUnmount() {
        console.log("Bye");
    }

    render() {
        return <h1>Bye, World</h1>;
    }
}

Conclusion

There are pros and cons in both styles but I would like to conclude that functional components are taking over modern React in the foreseeable future.

As we noticed in the examples, a functional component is written shorter and simpler, which makes it easier to develop, understand, and test. Class components can also be confusing with so many uses of this. Using functional components can easily avoid this kind of mess and keep everything clean.

It should also be noted that the React team is supporting more React hooks for functional components that replace or even improve upon class components. To follow up, the React team mentioned in earlier days that they will make performance optimizations in functional components by avoiding unnecessary checks and memory allocations. And as promising as it sounds, new hooks are recently introduced for functional components such as useState or useEffect while also promising that they are not going to obsolete class components. The team is seeking to gradually adopt functional components with hooks in newer cases, which means that there is no need to switch over the existing projects that utilize class components to the entire rewrite with functional components so that they can remain consistent.

Again, there are a lot of valid coding styles in React. Yet I prefer using functional components over class components for those reasons listed above. I hope this article helped you get more familiar with modern React. To learn more, check out the official documentation!