
TL;DR: Your react app is slow? Let's figure out some strategies in code to enhance its speed.
Check out more articles:
Applications can generally be categorized into having two types of performance bottlenecks:
Now, how do these classifications translate into the context of front-end applications, particularly React apps?
I/O Performance Challenges in React
When it comes to React applications, issues often arise in terms of I/O performance, primarily related to asynchronous HTTP calls. Ineffectively managing these network requests can lead to a slowdown in the application. While this post primarily focuses on CPU performance, it's essential to briefly touch upon key areas where solutions can be found for I/O-bound problems:
CPU Performance Challenges in React
The main thrust of this post centers around addressing CPU performance challenges in React. Before delving into the specifics, let's establish a concrete definition of performance:
In the context of React, this issue becomes critical. When a React component update is triggered, the entire subtree must be rendered in less than 30 ms. This becomes particularly challenging with complex and lengthy component structures, such as tables, trees, and lists, where large-scale re-renders may be necessary.
React Render and Commit Phase
React, at a high level, operates in two distinct phases:
Render Phase:
Commit Phase:
The subsequent exploration will focus on enhancing the efficiency of the Render phase. Before delving into optimization techniques, it is crucial to understand how to measure and identify the sluggish components in the application.
Measuring
Among the tools I frequently rely on are:
Chrome Dev Tool’s Performance Tab
This tool stands out as a comprehensive resource applicable to any browser application. It provides insights into frames per second, captures stack traces, identifies slow or hot sections of your code, and more. The primary user interface is represented by the flame chart.
For an in-depth understanding of Chrome’s Performance Tab as applied to React, refer to this documentation.
React Dev Tool’s Performance Tab
To leverage this tool, you'll need to install the React Dev Tool extension in your browser. It tailors information from the Chrome Dev Tool’s Performance Tab specifically to React. Through a flame chart, you can observe different commit phases and the JavaScript code executed during the respective render phase.
This tool aids in easily determining:
Measuring Methodology
Here’s the methodology I prefer when assessing front-end applications:
Identify the Problem:
Create a Hypothesis:
Measure:
Measure (Part II):
Create a Solution:
Measure the Solution:
Optimizing without proper measurement renders efforts practically ineffective. While some problems may be apparent, the majority necessitate thorough measurement, forming the cornerstone of the performance enhancement process.
Moreover, measurements empower you to communicate achievements upwards, informing users, stakeholders, and your leadership about performance improvements achieved within specific areas of your application, expressed as a percentage gain.
General Solutions to CPU-Bound Problems in React Applications
Now armed with measurements and an understanding of problematic areas, let’s delve into potential solutions. Optimizing React performance revolves around improving both what components render and which components render.
Many performance issues also stem from anti-patterns. Eliminating these anti-patterns, such as avoiding inline functional definitions in the render method, contributes to more efficient rendering times. Addressing poor patterns can, in fact, reduce complexity and improve performance simultaneously.
🤔 Improving What Components Render
Identifying sluggish components in our React app typically points to specific components that struggle with rendering or have an excessive number of instances on a single page. Various reasons may contribute to their sluggishness:
Most of these issues boil down to enhancing the speed of component rendering. At times, crucial components cannot rely on overly complex libraries, necessitating a return to basic principles and the implementation of simpler alternatives.
For instance, I encountered such challenges while using Formik excessively within multiple cells of every row in a complex table. While improving the efficiency of individual components goes a long way, attention must eventually shift to which components are rendering.
🧙 Improving Which Components Render
This aspect offers two broad categories for improvement:
Virtualization:
react-virtualized. Virtualization reduces the number of components React needs to render in a given frame.Props Optimization:
React.memo:
Most components in React can be memoized, ensuring that with the same props, the component returns the same tree (although hooks, state, and context are still respected). Leveraging React.memo informs React to skip re-rendering these memoized components if their props remain unchanged.
import React from 'react';
const MyComponent = React.memo((props) => {
// Component logic here
});
export default MyComponent;
Fake Prop Changes: useCallback:
Addressing the issue of fake prop changes involves instances where the content of a prop remains unchanged, but the reference changes. A classic example is an event handler.
import React, { useCallback } from 'react';
const MyComponent = () => {
const onChange = useCallback((e) => console.log(e), []);
return <input onChange={onChange} />;
};
export default MyComponent;
```
Fake Prop Changes: useMemo:
Similar challenges arise when constructing complex data structures without proper memoization before passing them as props. Utilizing useMemo ensures that rows are recalculated only when dependencies change, enhancing efficiency.
import React, { useMemo } from 'react';
const MyComponent = ({ data, deps }) => {
const rows = useMemo(() => data.filter(bySearchCriteria).sort(bySortOrder), [deps]);
return <Table data={rows} />;
};
export default MyComponent;
While you have the flexibility to customize how React.memo compares current vs. previous props, it's crucial to maintain a swift calculation since it's an integral part of the Render phase. Avoid overly complex deep comparisons during each render.
How it looks in the React dev tool:

Did they really? Are they fake prop changes? Use useCallback and useMemo.
How it looks in the React dev tool:

Use React.memo to memoize your pure components.
How it looks in the React dev tool:

Nothing too obvious to do here. Try to validate that the hook that changed makes sense. Perhaps a bad context provider is faking out changes the same way as fake prop changes might appear.