ReactJS Performance Optimization Techniques
Friday, July 12, 2024Every developer aims to make the most of the technology they use to develop apps and websites. With React, there might be some performance issues. But they can be easily resolved with ReactJS performance optimization techniques.
React development companies can leverage the rich ecosystem of the React library to deliver a reliable application with a responsive UI. Many top companies use React to empower their apps and websites. A React app can easily handle the state updates of a large web traffic flow only if you are familiar with the appropriate methods to resolve the issues.
This article sheds light on the best practices for React app performance optimization.
1. 12 Techniques to Optimize Your React Application
Here is a list of the best optimization techniques to improve your React performance.
1.1 Lazy Loading Images in React
Your application would contain several images. When a user opens a page, images are one of the heaviest elements on the page. So, it takes time for each of them to load completely. The images are displayed on the user screen only after DOM renders all the images together. This can slow down the performance of your app.
We can solve this problem by implementing Lazy loading, a technique that loads the image only when it appears on the user’s screen. This prevents the creation of unnecessary DOM nodes, which helps boost the performance of your React application.
Developers use popular lazy-loading libraries like react-lazy-load-image-component and react-lazyload for their React projects. Lazy loading improves the app loading time as it loads images whenever necessary rather than alongside the app page.
The code given below shows how to use react-lazy-load-image-component for lazy loading.
import { LazyLoadImage as LazyImage } from "react-lazy-load-image-component";
import "react-lazy-load-image-component/src/effects/blur.css";
export default function App() {
return (
);
}
1.2 Reduce Unnecessary Renders
Another reason why your app may slow down or become unresponsive is due to unnecessary re-renders. One method to reduce the number of rendering operations in your app is to use a Pure Component. The shouldComponentUpdate method is managed automatically here, providing a considerable boost to the React performance.
You can also leverage a couple of built-in hooks from React to reduce the number of renders in your app.
React.Memo:
Re-rendering in React occurs when the value of a prop or state changes. A prop in the child components often stays the same, but the prop of its parent component changes. React.memo() is a higher-order component that can be used to cache the output of the child component.
After implementing this hook, the child component will render only when any changes are made to its props. So, if a component has many props, it doesn’t need to be rendered frequently, thanks to this hook.
import React from 'react';
// A functional component with complex calculations
const ComplexComponent = ({ title }) => {
console.log('Rendering ComplexComponent');
return Welcome to, {title}!;
};
// Use of React.memo
const MemoizedComponent = React.memo(ComplexComponent);
const App = () => {
const [count, setCount] = React.useState(0);
const [title, setTitle] = React.useState('Website');
return (
);
};
export default App;
useCallback:
The useCallback hook returns the memoized version of the callback function. When the dependency changes, only then this callback would change. Optimized child components are dependent on reference equality. So, when these callbacks are passed to these child components, it helps prevent unnecessary renders.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
1.3 Spreading Props on DOM Elements
Unknown HTML attributes are added to the DOM elements when properties are spread into DOM elements. That is why the implementation of such should be eliminated.
const MessageText = props => {
return (
{props.message}
);
};
As an alternate solution to spreading props in DOM, you can set some specific attributes:
const MessageText = props => {
return (
{props.message}
);
};
1.4 Server-Side Rendering
Server-side rendering is beneficial as it helps deliver the viewable content more quickly compared to client-side rendering in the application. Although leveraging SSR improves the SEO and react app performance. There are also some downsides to it, like high setup costs and complex code.
If your app is data-intensive, SSR might eventually become one of the React performance issues for your React app. Though there are many good servers like NodeJS out there that can fulfill your requirements, NextJS is an ideal choice when it comes to server-side rendering. It helps save time in setting up the SSR and improves the loading speed.
Without SSR, your app page source would look the same as the code given below:
The Demo React App
Your app code is stored in the app.js bundle. The browser will fetch it and render the entire page within a couple of seconds. In Client-side rendering(CSR), the browser needs to make two round trips before the user can see the requested content. Additionally, if the app would have any API-driven data rendering then there will be further delay in the results.
But when you implement SSR, a trip to the server already takes place even before the user gets their requested content. So, when the browser requests a specific page, the server loads the React into memory. The server then fetches the necessary data for application rendering. The generated HTML is sent to the browser, which shows it to the user.
1.5 Dependency Optimization
Your app would have a few dependencies, that need to be checked to determine how much code comes from them. It helps optimize your app’s bundle size.
For instance, developers provide multi-language support in their app with MomentJS. But if this support is not actively utilized, then you can remove it from your final bundle by utilizing the moment-locales-webpack-plugin.
Similarly, if you have over 100 methods in your bundle but you are using around 20 then having all those unused methods would harm the React app performance. All of them can be removed using the lodash-webpack plugin.
1.6 Avoiding Props in Initial States
For setting an initial value for the state, you have to pass the prop with initial data to the React component. The code will look as shown below:
class MessageComponent extends Component {
constructor(props){
super(props);
this.state ={
isEnabled: false,
messageText: props.messageText
}
}
render(){
return
{this.state.messageText &&
Enter Message: >}
}
}
The new prop value won’t be assigned to the messageText state if the components aren’t refreshed when props are changed. This happens because the constructor is only called when the MessageComponent is created for the first time.
To quote React docs:
The source can sometimes be duplicated when the state of constructor function is initialized with a prop. Because the constructor is summoned upon the creation of the component.
Workaround:
So, instead of initializing the state using props, we have to directly use the props in the component.
class MessageComponent extends Component {
constructor(props){
super(props);
this.state ={
isEnabled: true
}
}
render(){
return {this.props.messageText &&
Enter Message:>}
}
}
The props keep changing, so when that happens, leverage the componentWillRecieve to update the state.
class MessageComponent extends Component {
constructor(props){
super(props);
this.state ={
isEnabled: false,
messageText: props.messageText
}
}
// reset state if the seeded prop is updated
componentWillReceiveProps(nextProps){
if (nextProps.messageText !== this.props.messageText) {
this.setState({ messageText: nextProps.messageText})
}
}
render(){
return {this.props.messageText &&
Enter Message: >}
}
}
1.7 Memoize React Components
Memoization is one of the best React performance optimization techniques that stores the result of an expensive function call and returns a cached result. So whenever the same execution is requested, this technique ensures the speed of the functions is optimized, instead of recomputing the logic.
For an example, consider a stateless UserDetail React component as given below:
const UserDetails = ({user, onEdit}) => {
const {titleText, full_ame, profileImg} = user;
return (
{fullName}
{titleText}
)
}
In UserDetails, every child component is based on props. So, whenever the props change, the stateless components have to re-render. If the attribute of the UserDetails component does not change, then we have to implement memoization on it.
import moize from 'moize';
const UserDetails = ({user, onEdit}) =>{
const {titleText, fullName, profileImg} = user;
return (
{fullName}
{titleText}
)
}
export default moize(UserDetails,{
isReact: true
});
It will compare the component’s references with the prop’s reference. Then, you can use the React.memo function (available only in React’s 16.6.0 or greater version) to execute the following code:
const UserDetails = ({user, onEdit}) => {
const {titleText, fullName, profileImg} = user;
return (
{fullName}
{titleText}
)
}
export default React.memo(UserDetails)
1.8 React.PureComponent
At a component level, there are two ways to optimize the React apps.
- By using function components: The overall bundle size can be reduced by using function components to prevent the construction of class instances.
- By using PureComponent: When you convert the function components to the PureComponent class, UI updates are optimized. React PureComponents are used to compare the values in the primitive data types and object references. While utilizing the React PureComponent, make sure these two criteria are met:
- Component State/Props are immutable object;
- State/Props should not have a multi-level nested object.
1.9 Avoid Inline Function
Using the render method to define functions can lead to unnecessary renders. As a result, your app performance degrades. When React does a reality check, the inline function always fails the prop diff when the functions in JavaScript are objects ({} !== {}). If used in a JSX property, a new instance would be created by the arrow function on every render. As a result, the garbage collector would have more clutter.
default class MessageList extends React.Component {
state = {
messages: [],
selectedMessageId: null
}
render(){
const { messages } = this.state;
return (
messages.map((message)=>{
return {
this.setState({selectedMessageId:message.messageId})
}} comment={message} key={message.id}/>
})
)
}
}
You can use arrow functions for concise event handling instead of defining inline functions for props.
default class MessageList extends React.Component {
state = {
messages: [],
selectedMessageId: null
}
onMessageClick = (messageId)=>{
this.setState({selectedMessageId:messageId})
}
render(){
const { messages } = this.state;
return (
messages.map((message)=>{
return
})
)
}
}
1.10 Use a Function in ‘SetState’
In the setState function, it is recommended that you use a function instead of an object. The reason behind this is that the state changes are not implemented in real-time, as conveyed in the React docs. Therefore, instead of using the code given below:
this.setState({correctData: !this.state.correctData});
Use the following code:
this.setState((prevState, props) => {
return {correctData: !prevState.correctData});
}
The previous state is received as the first argument when using the mentioned function in the previous state. On top of that, the props are taken as the second argument when the update is implemented.
1.11 List Virtualization in React Applications
The performance of your application slows down when you have several rows of items on your app page. Because the DOM will render all of them, even though not all items need to be displayed on the viewport.
List virtualization is a concept that allows you to render only those items visible to the user on the window, also known as windowing. Instead of rendering everything, only a small set of items are displayed. As the user keeps scrolling down and new items keep appearing on the window, they are rendered and replaced with the items that exited the viewport.
This way both the scrolling and rendering performance of the app is improved. Some of the windowing libraries developers prefer to use include react-virtualized and react-window.
1.12 CSS Animations Instead of JS Animations
Animations are added to the application to deliver an enhanced interactive user experience. There are many ways to create animations including using JavaScript, CSS animations, and CSS transitions.
However, each method must be used under certain scenarios to meet specific requirements. For example, you can use JS-based animation when –
- You want to add advanced effects like slow, bouncing, or rewind.
- You must have total control over your animations
- The animations should be triggered with a click or a mouseover.
- You have to change the visuals using requestAnimationFrame.
Meanwhile, use CSS-based animations when –
- Working with small UI elements that have discrete states like adding a hovering effect for items in the menu or displaying the tooltip.
- You have to add one-shot transitions such as toggling UI elements.
2. Things to Consider While Optimizing the React Performance
Before you get started with your React performance optimization journey, you should take a pause and understand why it lagged in the first place. Optimization shouldn’t be a reactive measure when your app malfunctions instead implement it as a continuous process. Understand the following things before optimizing your react performance.
2.1 What are the Main Issues Affecting React Performance Currently?
If you want this optimization to be successful, then you must have clarity on the performance issues of your React application. Developers can use performance and monitoring tools to monitor various metrics, such as the time it takes to render a component or to analyze the overall state of the application.
2.2 Which Components Should You Focus on to Optimize React Performance?
Knowing how your components work and interact with each other and how they are rendered will help you understand the problems as well. When you are monitoring the performance of the app and its components, you get real-time feedback on their conditions. Whenever an issue arises, these tools also send a detailed report about the problems.
Understanding these problems would help you prioritize the issues and which performance optimization technique to use to resolve them.
2.3 When to Use the shouldComponentUpdate Method in React?
Running and rendering virtual DOM, comparing previous and existing DOM, checking the differences between the components’ props and states, applying those changes, and re-rendering the DOM can be easily carried out to improve the performance of small React applications with only a few components.
But when your app grows and the number of components increases, the same processes will take time, and the one process meant to improve your performance will become responsible for further degrading it.
This issue can be resolved as React offers a method called shouldComponentUpdate. Developers can use it to set which component needs re-rendering.
function shouldComponentUpdate(nextProps, nextState) {
return true;
}
The render-diff process will be triggered if the method’s return value is true. You can control which components to render and which ones not to render by managing the render-diff method. You can do that by returning the false value using the above function. Then check out the existing and next state and prop to determine if rendering is necessary or not.
function shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
3. Top React Performance Optimization Tools
We previously discussed how you can leverage performance monitoring tools to find performance issues and state changes in your application. So, here are a few popular React tools.
3.1 React Dev Tools
React Dev Tools is a set of robust debugging and profiling tools. It enables you to analyze performance, track updates, and inspect the component hierarchies, props, state, and lifecycle methods to identify the source of the problem. If you use Chrome or Firefox then you can use React Dev Tools through its browser extension.
3.2 Chrome DevTools
Google Chrome browser provides a built-in set of web development tools called Chrome DevTools. It comes with a performance tab through which you can record a browser session, analyze information, and determine the performance bottlenecks. To access these tools, open the DevTools in the Chrome browser, press F12, or right-click on the page. Next, select “Inspect”.
3.3 Lighthouse
Another tool from Google, Lighthouse audits web performance and offers suggestions for improvements. This open-source and automated tool considers various metrics such as rendering efficiency, network utilization, and page load to evaluate a web page. It then generates a detailed report on accessibility, load performance, best practices, and so on. You can run this tool as a Chrome extension from Chrome DevTools or use it through the Command Line tool.
3.4 Redux
As a state management library, Redux helps you track the changes in your app’s state and allows you to handle them predictably and efficiently.
Further Reading on: Redux vs Redux Toolkit
3.5 Webpack
Webpack is a bundling tool supporting dynamic imports and code splitting. With this, you can break down your React app’s code into small chunks. This makes it easy to manage and scale your application. Moreover, you can quickly find out the source of the problem and rectify it immediately.
4. Conclusion
If you want your React app to provide an enhanced user experience with optimal performance, you have to implement the best ReactJS performance optimization techniques. React has a very rich ecosystem and offers tools and libraries that can help you with monitoring and analyzing the app’s performance. Remember, only effective implementation of React tools and techniques would help you get the maximum performance out of your application.
FAQs
How can you optimize the rendering of large lists in React?
You can optimize the rendering of large lists in React by leveraging infinite scrolling or pagination. Pagination breaks down the large list into multiple pages and renders only the current page. Meanwhile, infinite scrolling will keep loading more items for the user to scroll down and will remove the out-of-the-view items.
Why does the React app lag in performance?
The more frequently your React components re-render, the more your React app will lag in performance. It doesn’t matter even if those re-renders were essential. The increased number of re-renders harms the performance of the app. However, React also provides tools and functionalities necessary to optimize performance.
How does code splitting improve React application performance?
With code splitting, you can break down your code into smaller chunks. This simplifies the process of loading the only code required for a specific feature or route. It helps reduce the loading time and enhance the overall app performance.
Comments