Performance is essential when creating modern web applications since it improves user experience. The usage of React will unavoidably produce user interfaces that are intuitive, improving the user experience for many applications without the need for significant explicit performance optimization. However, developers frequently experience performance problems as the applications get bigger, and several techniques need to be employed to improve the performance of the React application.
In this article, you will look at tips that will help you optimize your React applications for better performance.
Code-splitting
The code files required to run a web application are combined to create the bundle file. The bundle expands in size when an application becomes more sophisticated, especially as the number and size of third-party libraries that are included rise. By design, this bundle file containing the whole application code loads and is served to users when a React application renders in a browser. The bundle can be divided into diverse smaller files to avoid the need to download enormous files at the initial page load, which slows down the application. This process of dividing code into several bundles that are loaded simultaneously or on request is called code-splitting.
The efficiency of your app can be significantly enhanced by "lazy-loading" only the components that the user needs at the moment with the aid of code-splitting. The amount of code in your program hasn't decreased overall, but you have avoided loading code that the user would never need and decreased the amount required at the initial load.
React's code-splitting feature enables us to divide a sizable bundle file into numerous chunks using dynamic import()
, then lazy load those chunks as needed with React.lazy
. With this approach, a sophisticated React application's performance is significantly enhanced.
To put code-splitting into practice, see the code below:
const Login = React.lazy(() => import("./components/Login"));
const Register = React.lazy(() => import("./components/Register"));
React will load every component dynamically with the code above. As a result, rather than loading a sizable bundle file for the entire application whenever the user visits any of the pages, React gets only the file for that particular page.
The Suspense
component should then encapsulate the rendered lazy-loaded components because it permits you to display fallback information (like a loading indicator/message) as you wait for the lazy-loaded component to render. This is done as follows:
<React.Suspense fallback={<p>Loading page...</p>}>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</React.Suspense>
Below is a demo of this in a code sandbox:
Virtualization of Large Lists
The performance of your software can be noticeably slowed down while rendering a massive table or list of data. It is because all of the objects are rendered in the DOM and may have an impact on how quickly our application responds, regardless of whether they appear in the browser viewport.
This is where Virtualization or windowing comes into play. Virtualization is a presentation technique that centers on watching the user's orientation and only publishing information to the DOM that is visually relevant at any specific scroll point. It is a method for showing lengthy lists of data more quickly. This technique can significantly decrease the amount of time it takes to re-render the components as well as the number of DOM nodes generated because it only renders a limited subset of items at a time.
There are several well-known React libraries available, such as react-window and react-virtualized, which offer a variety of modular components for rendering lists, grids, and tables.
Here is an example implemented with react-window:
First, install the package
npm i react-window
Then proceed with the implementation
Memoizing to avoid needless re-renders
Memoization is an optimization approach that is generally used to speed up computer programs by caching the outcomes of intensive commands and returning them when the same inputs are received again. A memoized function is typically faster since it will retrieve the result from the cache if it is called with the same parameters as the previous one rather than performing the function logic.
Memorization in React is used to improve app speed by eliminating unused renderings of components involved in the component hierarchy and by caching callbacks and values of expensive auxiliary methods.
React.memo()
React v16 was launched with a new API and a higher-order component named React.memo(). The documentation claims that this is just present to improve performance.
When a component gets created using React.memo(), it will typically only render if one or more of its props have changed; Although it is possible to override this, it just compares the props superficially to ensure this.
By preventing the re-rendering of components whose props have not changed or when it is unnecessary, React.memo() improves the efficiency of a React application.
React.memo() is used in the "Person" functional component below to stop the page from re-rendering.
export function Person({ firstName, lastName }) {
return (
<div>
<p>First Name: {firstName}</p>
<p>Last Name: {lastName}</p>
</div>
);
}
// memoized the person component
export const MemoizedPerson = React.memo(Person);
The functional component Person
in the code above displays a div
with a person's first and last name on a p
tag. Additionally, by naming a new function MemoizedPerson
, we are memoizing the Person
component. As long as the props, firstName
, and lastName
are the same on consecutive renders, memoizing the Person
component will prevent the component from re-rendering.
// On first render, React calls MemoizedPerson function.
<MemoizedPerson
firstName="John"
lastName="Doe"
/>
// On next render, React does not call MemoizedPerson function,
// preventing rendering
<MemoizedPerson
firstName="John"
lastName="Doe"
/>
// On next render, React calls MemoizedPerson function
// because one of the props changed
<MemoizedPerson
firstName="Jane"
lastName="Doe"
/>
React, in this case, makes a double call to the memoized function. If the props are unchanged, it won't re-render the component in the subsequent call.
When to utilize React.memo()
A pure functional component: If your component is functional, receives the same props, and consistently generates the same output, you can use React.memo(). React hooks enable the use of React.memo() on components that are not strictly functional.
The component renders frequently: React.memo() can be used to encapsulate a component that renders frequently.
The Component re-renders with the same parameters: If a component typically receives the same props during re-rendering, wrap it using React.memo().
A Component with Medium to high UI element: Use it to verify prop equality for components with a medium to a large number of UI elements.
Whenever we pass down primitive values like numbers, strings, booleans, React.memo()
functions fairly well. Non-primitive values, such as objects, arrays, and functions, on the other hand, never return true between redraws since they point to different locations in memory.
useMemo()
Use a useMemo Hook to store the value between renders when the prop we supply to a child component is an array or object. These are entirely new values that, as we learned above, point to distinct locations in memory.
Additionally, you may prevent re-calculating the same costly result in a component by employing the useMemo Hook. It enables us to memorize these values and only recompute them when the dependencies alter.
The useMemo()
Hook anticipates a function and a set of dependencies:
const memoizedValue = React.useMemo(() => {
// return expensive calculation
}, []);
Lazy-loading Images
You can avoid rendering all of the images at once in a React application comprising several photos to speed up page load time. By using lazy loading, you can delay rendering the images in the DOM until they are about to display in the viewport for each one of the images.
The lazy loading of photos avoids the introduction of redundant DOM nodes, improving the efficiency of your React application.
There are several well-known lazy-loading libraries for images used in React projects, which include react-lazy-load and react-lazy-load-image-component.
You can see an implementation done with react-lazy-load
below.
You need to install the package first:
npm i react-lazy-load
Then adding the functionality on a React app:
Utilize React fragments to reduce additional tags
In React, a new DOM node often gets formed when child elements are combined. However, this extra node degrades the app's performance and occasionally causes other issues.
React.Fragment
has the benefit of grouping many child elements without adding more nodes to the DOM. With React.Fragment
, you can aggregate a collection of related components without having to clutter the HTML with extra wrappers. As a result, you can write React elements that are clearer and spend less time creating layouts.
An example of React.Fragment
usage:
function App() {
return (
<React.Fragment>
<h1>Hello React!</h1>
<h1>Hello React Again!</h1>
</React.Fragment>
);
}
// The quick syntax <> </> can also be used to declare a fragment.
function App() {
return (
<>
<h1>Hello React!</h1>
<h1>Hello React Again!</h1>
</>
);
}
Conclusion
In this guide, You have gained knowledge about methods that will help you enhance the performance of your React application. Applying these suggestions correctly will allow you to significantly increase the speed of your React app. Although it can be difficult to optimize existing applications, the benefits far exceed the difficulties because the user experience is significantly enhanced.
I appreciate your time and hope you found this article useful.