Comparing speed and performance of functional and class-based components

Kunal Vishnoi
Aug 20 · 7 min read

Hooks solve a wide variety of seemingly unconnected problems in React that we’ve encountered over five years of writing and maintaining tens of thousands of components.

Whether you’re learning React, use it daily, or even prefer a different library with a similar component model, you might recognize some of these problems.

Prerequisite for React Hooks

Hooks are a new addition to React 16.8. It is not available prior to this version. A little knowledge about React is enough to understand React Hooks.

Why React Hooks

We’ve often had to maintain components that started out simple but grew into an unmanageable mess of stateful logic and side effects.

Each lifecycle method often contains a mix of unrelated logic. For example, components might perform data fetching in componentDidMount and componentDidUpdate.

However, the same componentDidMount method might also contain some unrelated logic that sets up event listeners, with cleanup performed in componentWillUnmount.

Mutually related code that changes together gets split apart but completely unrelated code ends up combined in a single method. This makes it too easy to introduce bugs and inconsistencies.

In many cases, it’s not possible to break these components into smaller ones because the stateful logic is all over the place. It’s also difficult to test them. This is one of the reasons many people prefer to combine React with a separate state management library.

However, that often introduces too much abstraction, requires you to jump between different files, and makes reusing components more difficult.

To solve this, Hooks let you split one component into smaller functions, based on which pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.

You may also opt into managing the component’s local state with a reducer to make it more predictable.

Hooks are lightning-fast

Hooks are very fast because if you create two components, one is a functional component that uses Hooks to manage state and the other is a class-based component.

We are going to make a basic counter in both components to compare their speed and performance. The first one is a class-based component, which simply performs a counter logic.

We checked the performance of this class-based component using audits in Chrome developer tools and it turns out to be 95%.

Refer to code here

Using Hooks in a functional component increases the performance to 98% which is 3% more than that of a class-based component.

We use the simple logic of the counter and it shows a difference of 3%. And, if the logic in the component is big, the performance difference is much more.

React Hooks

There are lots of React Hooks including useState, useEffect, useContext, useReducer, useMemo, useCallback, useRef, useImperativeHandle, useLayoutEffect, useDebugValue, and you can create your own custom Hooks.

We are going to run through some of them and compare them with a class-based component without Hooks.

useState Hook

We’ve already seen a basic example of useState Hooks in the counter logic above.

The useState Hooks return an array in which the first element is the state name and the second one is a function to modify the state value. In useState, we pass the initial value of the state.

const [count , setCount] = useState(0)
Refer to code here

This is how React Hooks manage state to store values similarly to class-based components.

The code for functional components is way smaller than that of class-based components. The same logic can be performed with the class-based component, as follows:

You can see that React Hooks minimize the code to almost half of the simple counter logic, compared to the class-based component.

useEffect Hook

The useEffect Hook is used to handle the lifecycle method in a functional component.

If you’re familiar with the React class lifecycle methods, you can think of the useEffect Hook as componentDidMount, componentDidUpdate, componentWillUnmount, and getDerivedStateFromProps combined as a beginner.

It helps to write the logic in one place itself. But Dan Abramov has repeated many times that useEffect shouldn’t be introduced as an alternative to these lifecycle methods only.

If you use useEffect with that mental model, you’ll get confused later. Instead, he suggests thinking of useEffect as a place where you run side-effects when a value in the dependency array changes.

useEffect is a callback function where we can give the second element as dependency array, which can contain anything like variables, props, state, etc., on which we have to run componentDidUpdate.

There are two common kinds of side effects in React components — those that don’t require cleanup and those that do.

This is a basic example of using componentDidMount and componentDidUpdate using a class-based component, which changes the title of the page based on the count value. This is an extension of the previous count logic.

This is how we can perform the same logic with Hooks. It decreases the code length almost to half and increases efficiency as well.

The dependency array comes into the picture when we have more than one state and we have to use the useEffect Hook only on a particular state change.

In the below example, we have two state counts and names and we have to update the document title only when our count value changes. That’s why a dependency array contains only count.

Complete code here

Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do.

For example, we might want to set up a subscription to an external data source. In that case, it is important to clean up so that we don’t introduce a memory leak!

For a cleanup function like componentWillUnmount, every effect may return a function that cleans up after it.

Refer to this example, which uses useState and useEffect Hooks to count the time left for the next day in HH:MM:SS format.

Code Link

useContext Hook

Accepts a context object (the value returned from React.createContext) and returns the current context value for that context.

The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.

const value = useContext(MyContext)

When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a re-render with the latest context value passed to that MyContext provider.

If you’re familiar with the context API before Hooks, useContext(MyContext) is equivalent to static contextType = MyContext in a class, or to <MyContext.Consumer>.

useContext(MyContext) only lets you read the context and subscribe to its changes. You still need a <MyContext.Provider> above in the tree to provide the value for this context.

For complete code refer this Link

useReducer Hook

An alternative to useState. Accepts a reducer of type (state, action) => newState and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

Complete code here

There are two different ways to initialize the useReducer state. You may choose either one depending on the use case. The simplest way is to pass the initial state as a second argument and the next one is by lazily.

useMemo Hook

If you need to cache the result of an invoked function, useMemo comes into the picture. It is helpful in performance optimization.

useMemo is a Hook that will only recompute the cached value when one of the dependencies has changed. This optimization leads to avoiding expensive calculations on every render.

Complete code here

In the above example, because of useMemo, the isEven operation will only happen when counterOne is incremented.

If useMemo was not there, it would happen even if counterTwo was changed and it decreases the efficiency as we add a heavy operation in the isEven function.

In the future, React may choose to “forget” some previously memoized values and recalculate them on the next render, e.g. to free memory for offscreen components.

Write your code so that it still works without useMemo — and then add it to optimize performance.

If you need to cache a function, useCallback comes into the picture. It is also used for performance optimization.

Custom Hooks

Building your own Hooks lets you extract component logic into reusable functions.

A custom Hook is a JavaScript function whose name starts with use and who may call other Hooks. For a complete understanding of custom Hooks refer to this documentation.

I hope you enjoyed reading my first article.

Better Programming

Advice for programmers.

Thanks to Aditya Agarwal

Kunal Vishnoi

Written by

Intern at GradeUp | ReactJs Developer | Looking for fulltime opportunity

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade