React Functional Component with Hooks: Everything You Need To Know

Muhammad Syakirurohman
Frontend Engineer
Published on
React Functional Component with Hooks: Everything You Need To Know

As you might know, there are two ways to create a component in React, with functional component and class-based component.

Before React 16.8 released on February 16, 2019, we always use a class-based component to create a stateful component (a React component with states). Back then, a functional component only used when we create a stateless component.

Now, since React Hooks introduced in version 16.8, we can create a stateful component without declaring a class. We can “hook into” React state and lifecycle features from a function component with Hooks.

What is Functional Component in React?

A Functional Component is a React Component declared with a plain javascript function that takes props and returns JSX. Before Hooks introduced, it’s also known as a Stateless Component.

Now, we can’t call it a stateless component anymore since it can also have states and lifecycles.

With the existence of Hooks, React functional component can replace the class-based component as it’s easier and shorter to write, easy to test, and has better performance.

How to Write React Functional Component?

We can create any type of react component with a functional component, from a stateless component to a complex component that has states and lifecycles.

1. A Simple Stateless Component

A simple stateless component usually created when you need a reusable UI that don’t have any props/inputs or states.

This is a very basic component that you better write it as a functional component.

import React from 'react';
 
export default function StatelessComponent() {
	return <div>I am a stateless component</div>;
}

2. Handling Props

Suppose you want to add a stateless component with name and role prop. It will be called in the other components like this.

<StatelessComponent name='Syakir' role='Front-end Developer' />

To handle inputs/props provided, you can access them as follows.

import React from 'react';
 
export default function StatelessComponent(props) {
	return (
		<div>
			Hi, I am {props.name}
			<br />I am a {props.role}
		</div>
	);
}

In a functional component, props passed through an argument (as object) that stores any input/prop as its property.

3. Props with PropTypes

To create a better component, you should define and validate the props. We can use PropTypes for that purpose.

import React from 'react';
import PropTypes from 'prop-types';
 
export default function StatelessComponent(props) {
	return (
		<div>
			Hi, I am {props.name}
			<br />I am a {props.age} years old {props.role}
		</div>
	);
}
 
StatelessComponent.propTypes = {
	name: PropTypes.string.isRequired,
	role: PropTypes.string.isRequired,
	age: PropTypes.number.isRequired
};

With PropTypes, you can type-checking your props easily. If the props provided don’t match with defined type, it will trigger a warning.

For more details about PropTypes, you can go to this page.

4. A Stateful Component (with useState Hook)

Now, we can create a stateful functional component by using useState Hook.

Here is how you can use it.

import React, { useState } from 'react';
 
export default function StatefulComponent() {
	const [helloMessage, setHelloMessage] = useState('Hello world!');
 
	return (
		<div>
			<input type='text' value={helloMessage} />
		</div>
	);
}

useState hook used to declare a “state variable”. It returns a pair of values: the current state (helloMessage), and a function that update it (setHelloMessage).

They are equivalent to this.state.helloMessage and this.setState in the class-based component.

5. Handling Event

When users interact with components like form, button, link, etc, we want them to behave as we want.

Therefore, we need event handlers to handle events like onClick, onKeyup, onChange, and other supported react events here.

For instance, we want to change helloMessage when users change the value of the input field. You can do that as follows.

import React, { useState } from 'react';
 
export default function StatefulComponent() {
	const [helloMessage, setHelloMessage] = useState('Hello world!');
 
	return (
		<div>
			<input type='text' value={helloMessage} onChange={(e) => setHelloMessage(e.target.value)} />
			{helloMessage}
		</div>
	);
}

Because we only need one line code to change the state, we can write the event handler inline as an arrow function.

If you want to add other codes when the input changes, you better write the event handler as a separate function as follows.

import React, { useState } from 'react';
 
export default function StatefulComponent() {
	const [helloMessage, setHelloMessage] = useState('Hello world!');
 
	const onInputChange = (e) => {
		setHelloMessage(e.target.value);
		// other codes
	};
 
	return (
		<div>
			<input type='text' value={helloMessage} onChange={onInputChange} />
			{helloMessage}
		</div>
	);
}

6. Handling Callback (Passing data from child to parent component)

In the real project, we often wrapping a component inside another component(parent component).

In many cases, we need to listen to what happened in child component, and create a handler in the parent component. Simply, we need to pass data from child to parent component.

We can do that with callback function.

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

https://developer.mozilla.org/en-US/docs/Glossary/Callback\_function

Here is how you can listen or pass data from child to parent with callback function.

import React, { useState } from 'react';
 
export default function ParentComponent() {
	const [messageFromChild, setMessageFromChild] = useState('');
 
	return (
		<div>
			parent should listen 'messageFromChild' when it changed: {messageFromChild}
			<br />
			<br />
			<ChildComponent onChangeHelloMessage={(e) => setMessageFromChild(e)} />
		</div>
	);
}
 
function ChildComponent({ onChangeHelloMessage }) {
	const [helloMessage, setHelloMessage] = useState('Hello world!');
 
	const onInputChange = (e) => {
		setHelloMessage(e.target.value);
		onChangeHelloMessage(e.target.value);
	};
 
	return (
		<div>
			<input type='text' value={helloMessage} onChange={onInputChange} />
			{helloMessage}
		</div>
	);
}

The callback function in the codes above is onChangeHelloMessage that passed as a prop in ChildComponent. The onChangeHelloMessage value is invoked in the onInputChange function.

Callback functions are also used frequently to create a reusable component that all of its states depend on the parent component who calls it.

For instance, we create a customized input component (eg. autocomplete, masked input) that shared across components.

import React, { useState } from 'react';
 
export default function ParentComponent() {
	const [customizedInputValue, setCustomizedInputValue] = useState('');
 
	return (
		<div>
			<ACustomizedInputComponent value={customizedInputValue} onChangeValue={(e) => setCustomizedInputValue(e.target.value)} />
			<br />
			{customizedInputValue}
		</div>
	);
}
 
function ACustomizedInputComponent({ value, onChangeValue }) {
	// Write some functions here that make this as a customized component.
	return (
		<div>
			{/* Just pretend this is a customized input that can't handled with the common input field */}
			<input type='text' value={value} onChange={onChangeValue} />
		</div>
	);
}

As you can see, the ParentComponent control the states that passed to ACustomizedInputComponent and listen to the change made in ACustomizedInputComponent.

7. Functional Component Lifecycle (useEffect Hook)

In a class-based component, there are lifecycle methods like componentDidMount, componentDidUpdate and componentWillUnmount.

Thanks to useEffect hook, we can now have the equivalent function to replace them.

By using useEffect, you tell React that your component needs to do something after render. React will remember the function you passed, and call it later after performing the DOM updates.

In the real project, the useEffect hook usually used to wrap an API calling function. You can see the usage in my tutorial about React Infinite scrolling.

For the simple example, you can look at the codes below.

import React, { useState, useEffect } from 'react';
 
function Example() {
	const [count, setCount] = useState(0);
 
	useEffect(() => {
		document.title = `You clicked ${count} times`;
	});
 
	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={() => setCount(count + 1)}>Click me</button>
		</div>
	);
}

This is equivalent to the codes below (in class-based component).

class Example extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		};
	}
 
	componentDidMount() {
		document.title = `You clicked ${this.state.count} times`;
	}
	componentDidUpdate() {
		document.title = `You clicked ${this.state.count} times`;
	}
 
	render() {
		return (
			<div>
				<p>You clicked {this.state.count} times</p>
				<button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
			</div>
		);
	}
}

For further usage of useEffect, this post from Adrian Bece might help you to understand that.

Why should you use Functional Component instead of Class Component?

If you are still have a doubt to adopt functional components as a whole in your React App, here are the complete reasons why you should use Functional Components instead of Class Components.

1. Easier to Read, Shorter to Write

Compared to Class-based components, Functional components are easier to understand and shorter to write. Look at the codes below.

import React from 'react';
 
class Example extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		};
	}
 
	render() {
		return (
			<div>
				<p>You clicked {this.state.count} times</p>
				<button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
			</div>
		);
	}
}
import React, { useState } from 'react';
 
function Example() {
	const [count, setCount] = useState(0);
 
	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={() => setCount(count + 1)}>Click me</button>
		</div>
	);
}

Both code blocks are the same components but declared different ways. The first code block declared with a class-based component, while the second code block declared with a functional component.

With the functional component, you only need 14 lines of code for the Example component. On the other hand, you will have to write 24 lines of code if you declare it with a class-based component.

The shorter codes are easier to read.

2. Easier to Test

Since it is written as a plain javascript function, you can test a functional component like you test a function.

You don’t also have to worry about hidden state or side effects. For every input (props), functional components have exactly one output.

3. Potentially have Better Performance

In the release notes of function component, it said,

In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations

This article also said that functional components are 45% faster than class-based component, even without optimization.

4. Enforced Best Practices

A functional component often used to create a presentational component that focuses on UI rather than behavior.

We should avoid using the state for this kind of component. States and lifecyles should be used in the higher level component

By using a functional component (as a stateless component), you keep your presentational component pure, without states and lifecycles.

5. The Future of React

Since Hooks introduced, many developers choose to use functional components because now it can do nearly everything that a class-based component can do.

So, why still use class-based components while functional components are better and many developers love it?