Functional VS class based components in React

With the release of React hooks we now have an alternative (and cleaner) way to write our components. Today we’ll take a look into the new syntax, make comparisons to traditional class components and discuss the pros and cons of this new approach.

Note:
This post is aimed to compliment your journey into learning React, you’ll still need a good understanding of the library. For that the official React documentation is a good start.

Why change to functional based components?

Until now we’ve been writing our React components using JavaScript classes. Support for this approach is not going away and not all situations will be suited to the functional approach. So why change?

Traditional class based components are inherently more complex to write, they can become difficult to understand as they grow in size and will cause confusion amongst less experienced team members.

The React team were aware of these issues and as of version 16.8 React hooks became part of the library. React hooks allow developers to fully embrace functional component syntax, whilst allowing support for state and component lifecycle, which is normally reserved for class based components.

Note:
Functional components are not to be confused with the concept of presentational and smart components. When we refer to a functional component, we mean a React component that is written as a JavaScript function instead of a class.

Basic implementation

Let’s start by writing a simple React component as both a class and then function. The component takes a name property and returns a string wrapped in a h1 tag. We export the component for use within our application.

Class based component

import React from 'react'

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

Looks familiar right? Now let’s refactor as a functional component.

Functional component

import React from 'react'

export const Hello = (props) => {
	return (
		<h1>Hello, {props.name}<h1>
	)
}

Much cleaner right? Easier to understand too, it’s just a standard JavaScript function. We’ve even shaved 2 lines off our code in the process, great!

The more experienced developers reading this may question the fanfare, after all, we’ve been able to write our components as functions long before the inclusion of React hooks. That’s a valid observation, however, functional components did not originally support state and component lifecycle, if you needed it, you’d have to use a class.

React hooks now allow developers to embrace the elegance and simplicity of a functional component, whilst leveraging state and component lifecycle. Let’s explore those concepts now.

Component state

We’ll now write a new React component, it’ll be a counter that iterates a count on each click of a button. The component will store the value of the count in local state. Let’s first write this as a class based component.

Class based component

import React from 'react'

export class Counter extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		};
		this.handleClick = this.handleClick.bind(this);
	}
	
	handleClick() {
		this.setState(state => ({
			count: this.state.count +1
		}));
	}
	
	render() {
		return (
			<div>
				<p>You clicked the button {this.state.count} times</p>
				<button onClick={this.handleClick}>Click me</button>
			</div>
		)
	}
}

That’s quite a lot of code for such a simple component. Let’s refactor this to a functional component and see how it compares.

Functional component

import React, { useState } from 'react'

export const Counter = (props) => {
	const [count, setCount] = useState(0);

	const handleClick = () => setCount(count + 1) 
	
	return(
		<div>
			<p>You clicked the button {count} times</p>
			<button onClick={handleClick}>Click me</button>
		</div>
	)	
}

What do you prefer? There’s no doubt the functional approach is better. We’ve managed to reduce the amount of code we need to write and make the component easier to understand, all whilst still maintaining the same functionality.

Tip:
Did you notice the line const [count, setCount] = useState(0)? This is the magic that allows the use of the state hook. We are effectively declaring a getter and setter using array destructing (count and setCount). We then pass an initial value for our count state by calling useState(0). It’s both an elegant and simple approach in comparison to what’s required when using a class based component.

You can learn more about the state hook at the official documentation

Component lifecycle

We’ve touched on state, but what about component lifecycle? Traditional methods like componentDidMount or componentDidUpdate are still available, to access this however, we use a hook called useEffect (sometimes referred to as a “side effect”). useEffect is executed on first render and after each subsequent re-render, which is why it can be compared to the traditional class based lifecycle methods mentioned above.

Let’s extend our counter component to render the count value to the document title. We’ll optimise performance by putting measures in-place to only update the title if the value has changed.

Class based component

import React from 'react'

export class Counter extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		};
		this.handleClick = this.handleClick.bind(this);
	}
	
	componentDidMount() {
		document.title = `You clicked ${this.state.count} times`;
	}
	
	componentDidUpdate(prevProps, prevState) {
		if(prevState.count !== this.state.count) {
			document.title = `You clicked ${this.state.count} times`;
		}
	}
	
	handleClick() {
		this.setState(state => ({
			count: this.state.count +1
		}));
	}
	
	render() {
		return (
			<div>
				<p>You clicked the button {this.state.count} times</p>
				<button onClick={this.handleClick}>Click me</button>
			</div>
		)
	}
}

Here we’re using two lifecycle methods to set and update the document title to reflect the amount of times the button has been clicked. We pass in prevState when calling componentDidUpdate to allow us to make a comparison, if the value of count has not changed, we skip updating the title.

As you can see, we’ve had to write quite a few lines, including a bit of repetition. Now let’s see how a functional component using useEffect compares.

Functional component

import React, { useState, useEffect } from 'react'

export const Counter = (props) => {
	const [count, setCount] = useState(0);
	
	useEffect(() => {
		document.title = `You clicked ${count} times`;
	}, [count]);

	const handleClick = () => setCount(count + 1) 
	
	return(
		<div>
			<p>You clicked the button {count} times</p>
			<button onClick={handleClick}>Click me</button>
		</div>
	)	
}

We’ve managed to replicate componentDidMount and componentDidUpdate with one call to useEffect. It’s reduced the amount of code we’ve had to write, prevented repetition and made the component much easier to read.

Tip:
Have you noticed we passed [count] as the second argument to useEffect? We’re effectively replicating the comparison we run on our class component if(prevState.count !== this.state.count), albeit React is automatically doing the comparison for us using the supplied value of [count].

You can learn more about the effect hook at the official documentation

Conclusion

Today we’ve learnt about the differences between class and functional based React components, including; state and lifecycle. As with all tutorials, you should review the official documentation, then practice these concepts to gain more understanding and context on how they can be used within your own project.

That’s all folks, thanks for reading.