Functional React Components Don’t Need To Be Stateless

groana
5 min readDec 30, 2020

React Hooks are functions that allow you to use React state and lifecycle methods with functional components. It was introduced in 2018 with React 16.8.0. Before this, the only way to use state was through class components. Now there’s no need to convert a functional component into a class component in order to manipulate state.

Hooks have become a popular way of coding and for good reason. They reduce the number of concepts you need to juggle when writing React applications. No more switching back and forth between functions, classes, higher-order components, and render props. They’re ideal if you’ve written a functional component and realize you need to add state to it.

Hooks also let you split a component into smaller functions based on its related parts. When fetching data, the code can be contained in one function as opposed to duplicating it inside of lifecycle methods like componentDidMount and componentDidUpdate. Local state can also be managed with a reducer to make it more predictable.

When creating a React component I begin by creating a class as a way to cover my bases. With a class I know I can declare state, and use lifecycle methods without needing to worry about switching from a function to a class in the middle of my project. But, in a recent build, I ran into trouble. When learning a new technology a lot of your time is spent googling ways to implement the tech. When I researched resources to build my app I found most of the React solutions I came across used functional components. At first glance, this was no cause for concern, but things got out of hand when I realized the functional components were manipulating state and using methods like useState! How was this possible? What was useState? This is how I was introduced to hooks.

Below we’ll convert a functional component into a class component

For a recent group project, I created a React Native app that generates a list of user favorites when a user is logged in. This is the code for the UserFavesScreen:

When we finish refactoring this code, the app will have the same functionality, but with shorter and more organized code.

This is a class component that takes props as an argument, declares state, has a few methods that need to be bound in the constructor and uses the componentDidMount lifecycle to create effects. Since we need to use state, and a lifecycle method we will import useState and useEffect from react.

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

Next, I refactor my code into a function — converting all of the methods, removing the render, and commenting out code I need to go back and refactor. Since the constructor has props passed down to it, when I refactor my class I need to add props as the parameter of the function. (Note: I like to refactor the more difficult code at the end.)

Next, I will declare my state with the useState hook.

useState returns a pair — the current state value and a function that lets you update it. It can be called from an event handler or somewhere else. It doesn’t merge old and new state together, and it can be called more than once in a single component. It uses array destructuring syntax that gives different names to the state variables we declare by calling useState. These names aren’t a part of the useState API.

useState takes the initial value and whenever you need to change state, you just call the second element in the array destructuring.

const [refreshing, setRefreshing] = useState(false);const [favorites, setFavorites] = useState([]);

Now that we’ve taken care of our state, we can go ahead and remove any references of this in the component. Since our component is no longer a class, there is no need for this.state nor this.props. Instead, we use the variable name of the state, and instead of using this.setState, we will call the second variable for setting state.

Since lifecycle methods cannot be used inside of functional components, we use useEffect instead to perform side effects in functional components. Side effects are operations where you fetch data or manually change the DOM — operations that can affect other components and can’t be done during render. Hooks like useEffect, useContext allow you to organize effects in a component by pieces that are related to each other rather than needing to split them based on lifecycle methods.

useEffect takes a callback as a parameter. React runs the effects after every render of the component, including the first render!

But things get tricky when we need to use async/await. The original code we’re trying to refactor runs an asynchronous function in the componentDidMount. Seems easy, why can’t we just rewrite it like this:

The above code returns the error: “An effect function must not return anything besides a function, which is used for clean-up.” useEffect cannot return a promise, instead we will write a function inside of this hook and call that function inside of it.

This is better, but what happens when the user logs out or when a new user logs in? The user favorites screen is still populated with the previous user’s data. When running the app for the first time, this means the screen will always be empty!

This is where dependencies come in.

The second optional parameter of useEffect is an array of dependencies. When we keep the array empty we’re asking useEffect to run once when the component loads and not to run again. When a variable is placed inside of this hook, useEffect will run every time that variable changes. In our case, this component is dependent on the user, specifically the props.user.uid. By placing props.user.uid we’re asking useEffect to run every time the user changes.

This can easily get confusing. A good general rule to follow is, anytime a variable is being pulled into useEffect from outside its scope, you must include it inside the array of dependencies. This will state that useEffect is dependent on this variable, and every time the value of that variable changes, useEffect will update and run again.

Here’s the final code:

And there you have it — converting a class component that manages state and uses an asynchronous componentDidMount into a functional component.

The full code can be found here.

--

--