Take your React App to the next level
Introduction
When building any React applications, a lot of thought goes into how the application should work and end up looking. The least any team or developer needs to do is to check the performance and look out for techniques to optimize the app for the end user’s experience. A lot of times you overlook this action, but in this article, I’ll be sharing a few ways you can start optimizing your application for better performance.
React is a JavaScript library for building user interfaces. React ships with several ways to minimize the number of costly DOM operations required to update the UI. For many applications, using React will lead to a fast user interface without doing much work to specifically optimize for performance. Nevertheless, there are several ways you can speed up your React application. Let’s dive in and learn some of these techniques.
1. Use React.Fragment
to Avoid Adding Extra Nodes to the DOM
When working with React, there are cases where you will need to render multiple elements or return a group of related items. Here’s an example:
function App() {
return (
<h1>Hello React!</h1>
<h1>Hello React Again!</h1>
);
}
If you try to run your app with the code above, you will run into an error stating that Adjacent JSX elements must be wrapped in an enclosing tag.
This implies that you need to wrap both elements within a parent div.
function App() {
return (
<div>
<h1>Hello React!</h1>
<h1>Hello React Again!</h1>
</div>
);
}
Doing this will fix the error but comes with a degree of risk. You are adding an extra node to the DOM, which is unnecessary. In a case like this, where the above is a child component that will be enclosed within a parent component, this becomes a problem.
function Table() {
return (
<table>
<td>This is a Table Component</td>
<Columns />
</table>
);
}
function Columns() {
return (
<div>
<td>Hello React!</td>
<td>Hello React Again!</td>
</div>
);
}
The resulting HTML for the Table component
will be invalid because of the additional div that was added.
function Table() {
return (
<table>
<td>This is a Table Component</td>
<div>
<td>Hello React!</td>
<td>Hello React Again!</td>
</div>
</table>
);
}
Let’s take a look at a better way of solving this by using React Fragment, which will not add any additional node to the DOM. The syntax looks like this:
function Columns() {
return (
<React.Fragment>
<td>Hello React!</td>
<td>Hello React Again!</td>
</React.Fragment>
);
}
You can also use the short syntax <></>
for declaring a Fragment.
function Columns() {
return (
<>
<td>Hello React!</td>
<td>Hello React Again!</td>
</>
);
}
2. Use React.Suspense and React.Lazy for Lazy Loading Components
Lazy loading is a great technique for optimizing and speeding up the render time of your app. The idea of lazy loading is to load a component only when it is needed. React comes bundled with the React.lazy
API so that you can render a dynamic import as a regular component. Here instead of loading your normal component like this:
import LazyComponent from './LazyComponent';
You can cut down the risk of performance by using the lazy method to render a component.
const LazyComponent = React.lazy(() => import('./LazyComponent'));
React.lazy
takes a function that must call a dynamic import()
. This will then return a Promise
which resolves to a module with an default
export containing a React component.
The lazy component should be rendered inside a Suspense
component, which allows you to add fallback content as a loading state while waiting for the lazy component to load.
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading....</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
3. Use React.memo for Component Memoization
React.memo is a great way of optimizing performance as it helps cache functional components.
In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
Here’s how it works: When a function is rendered using this technique, it saves the result in memory, and the next time the function with the same arguments is called it returns the saved result without executing the function again, saving you bandwidth.
In the context of React, functions are the functional components, and arguments are props. Here’s an example:
import React from 'react';
const MyComponent = React.memo(props => {
/* render only if the props changed */
});
React.memo is a higher-order component and it’s similar to React.PureComponent but for using function components instead of classes.
When to use:
- When we expect that a functional component will often render after the first render, usually with the same props. A situation when this can occur is if a parent component forces a child component to render. In such cases, it would be appropriate to apply memoization to your components.
- When we have pure functional components. Given the same props, such components will remain the same — always rendering the same output.
- When our component(s) are large in size and contain a good number of UI elements such that a re-render would cause a response delay that would be perceptible to the user.
When not to use:
- The first and the most obvious case where we should avoid using React.memo() is when we cannot possibly quantify the performance gains from it. If the computational time of our component’s re-render — with and without this higher-order component is negligible or non-existent, then using React.memo() would be useless.
- In some cases, we would want a component to re-render with different props. React.memo() by default performs a props equality check to assert whether a component’s render props are equal. The equality will only return true if the previous props are equal to the next props. If our component re-renders with different props, then this equality check will return false, forcing React to perform the difference of the previous and the next props. Such a ‘volatile’ component should not be wrapped in React.memo() due to this useless props comparison, and the fact that React will always perform 2 jobs on every render. This not only has no performance gain, but also harms performance!
- Whereas wrapping class components in React.memo() is possible, it is considered (and rightly so) a bad practice and is highly discouraged. Instead of this, extending a PureComponent class is more desirable and a much cleaner way of handling memoization when working with class components.
Use cases and avoidance situations of React.memo() include but are not limited to the above-mentioned instances.
Strictly use React.memo() as a performance boost for applications with components that re-render often. However, keep in mind that React components will always re-render if the state changes — regardless of whether the component is wrapped in React.memo() or not.
4. Dependency optimization
It’s good to analyze how much code you’re using from dependencies while optimizing react app bundle size. You may, for example, be using Moment.js, which offers multi-language localized files. If you don’t need to support several languages, you can remove unnecessary locales from the final bundle with the moment-locales-webpack-plugin.
Let’s take the case of loadash, where you’re only using 20 of the 100+ methods available. Having all those other approaches isn’t ideal in that case. You may do this by removing unneeded functions with the loadash-webpack-plugin.
From here, you can download the list of dependencies you can optimize.
5. Avoid using Index as Key for map
You often see indexes being used as a key when rendering a list.
{
comments.map((comment, index) => {
<Comment
{..comment}
key={index} />
})
}
But using the key as the index can show your app incorrect data as it is being used to identify DOM elements. When you push or remove an item from the list if the key is the same as before, React assumes that the DOM element represents the same component.
It’s always advisable to use a unique property as a key, or if your data doesn’t have any unique attributes, then you can think of using the shortid module
which generates a unique key.
import shortid from "shortid";{
comments.map((comment, index) => {
<Comment
{..comment}
key={shortid.generate()} />
})
}
However, if the data has a unique property, such as an ID, then it’s better to use that property.
{
comments.map((comment, index) => {
<Comment
{..comment}
key={comment.id} />
})
}
In certain cases, it’s completely okay to use the index as the key, but only if the below condition holds:
- The list and items are static
- The items in the list don’t have IDs and the list is never going to be reordered or filtered
- List is immutable
Wrapping up
These are some of the things useful to boost your application performance. Do let us know in the comments which one helped you the most.