Table of Contents
Optimizing application performance is a critical aspect of developing web applications. Users expect applications to load quickly and respond to their interactions smoothly.
Internally, React uses several techniques to minimize the number of costly DOM operations required to update the UI. We are guaranteed a very fast UI by default. However as the application grows there are ways where we can still speed up the application with various performance optimization techniques, significantly enhancing the user experience by reducing load times and improving responsivenes.
Optimizing the performance is important in React Application for several reasons:
Faster application provides better user experience. User expect the web application to be reponsiveness and snappy rather than slow-loading and laggy, which leads to a poor user experience.
Page load time is a significant factor in search engine ranking algorithms, Faster application tend to rank higher in search engine results pages.Hence a well-optimized application will rank higher in search results, making it more visible to potential users.
Slow-loading web applicaton often results in higher bounce rates, users will likely leave and never return. By optimizing react application we can retain user's attention by loading quickly and providing seamless browsing experience.
According to research done by Portent, a website that loads within one second has a conversion rate five times higher than a site that takes ten seconds to load. Therefore a fast and efficient application sets you apart from competitors whose application may be slower or less optimized.
Below are the optimmization techniques that be added to make the react application fast.
Memoization in React is a performance optimization technique within functional components. It involves storing the results of expensive computations or function calls in cache to avoid redundant calculation when the same values are encountered. This approach improves the efficiency of the application.
In React, there are three techniques for memoization: React.memo()
, useMemo()
and useCallback()
.
React.memo is a higher order component provided by React that is used to memoize the functional components.It prevent the re-rendering of the component if the recieved props remain unchanged.
Here is an example on how React.memo() is used
import React from "react";
const Greeting = ({ name }) => {
return <div>Hello, {name}!</div>;
};
export default React.memo(Greeting);
In the above code we have a simple functional component Greeting
that takes a name
prop and displays a greeting message. We implemented React.memo()
to wrap the Greeting
, by wrapping it react will only re-render the Greeting
component if the name
props changes.
import React, { useState } from "react";
const ParentComponent = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<Greeting name="John" />
<p>Count: {count}</p>
</div>
);
};
export default ParentComponent;
We have a parent component ParentComponent
that maintains a count state using useState()
. It renders the Greeting
component along with a button to increment the count.
When you run this code and click the "Increment Count" button, you'll notice that the Greeting
component is only rendered once. This is because React.memo()
memoizes the rendering of the component based on its props. Since the props passed to Greeting
remain the same (name="John"), React skips re-rendering the component on subsequent updates triggered by the count increment.
However, if you remove React.memo()
and use Greeting
directly in ParentComponent
, you'll see that Greeting
is rendered on every count increment, regardless of whether its props have changed or not. This demonstrates the performance optimization achieved by memoization with React.memo()
.
The useMemo()
is a hook provided by React that memoizes the result a function call of an expensive computation.
Below is an example on how to use the useMemo hook.
import React, { useMemo } from "react";
const SumComponent = ({ numbers }) => {
const sum = useMemo(() => {
return numbers.reduce((acc, num) => acc + num, 0);
}, [numbers]);
return (
<div>
<h2>Sum of Numbers</h2>
<p>{sum}</p>
</div>
);
};
export default SumComponent;
In the above example we have a SumComponent
functional component that takes a list of numbers as a prop. Inside this component, we use React.useMemo()
to memoize the calculation of the sum of the numbers.
Here the dependency array of useMemo()
is [numbers]
, which means that the memoized value sum
will only be recalculated when the numbers array changes.
import React, { useState } from "react";
const App = () => {
const [numberList, setNumberList] = useState([1, 2, 3, 4, 5]);
const addRandomNumber = () => {
const randomNumber = Math.floor(Math.random() * 10) + 1;
setNumberList([...numberList, randomNumber]);
};
return (
<div>
<button onClick={addRandomNumber}>Add Random Number</button>
<ul>
{numberList.map((number, index) => (
<li key={index}>{number}</li>
))}
</ul>
<SumComponent numbers={numberList} />
</div>
);
};
export default App;
The App
component maintains a state numberList
to hold the list of numbers. When the Add Random Number
button is clicked, a random number is added to the list and the SumComponent
is rendered with the numberList
as its prop.
Taking the above example when we run this code and click the Add Random Number
button, you'll notice that the sum
is recalculated only when the list of numbers changes. The calculation of the sum is memoized using React.useMemo()
, which helps in optimizing performance by avoiding unnecessary recalculations.
The useCallback()
hook in React is used to memoize a function instead of memoizing the function result. The useCallback()
memoizes the function, ensuring it remains the same across re-renders as long as the dependencies haven't changed.
Below is the example where the hook is being used :
import React, { useState, useCallback } from "react";
const ChildComponent = ({ onClick }) => {
return <button onClick={onClick}>Click Me</button>;
};
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h2>Parent Component</h2>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
};
export default ParentComponent;
We have a ChildComponent
functional component that receives a onClick callback function as a prop. This component thenrenders a button that triggers the onClick callback when clicked.
We have a ParentComponent which renders the ChildComponent and alsi maintains a count
state using the useState()
hook.
Inside the ParentComponent
, we have used React.useCallback()
to memoize the handleClick
callback function. The dependency [count]
specifies that the callback function should be recalculated only if the value of count state changes.
Taking the above implementation when we run the code and click the "Click Me", only when the button is clicked the count value is updated. The handleClick callback function is memoized using React.useCallback()
, preventing unnecessary recreations of the function on each render of the parent component. This helps in optimizing performance by avoiding unnecessary re-renders of child components that receive the callback function as a prop.
Windowing or List Virtualization refers to a technique used particularly in user interface design, to efficiently manage dealing with large number of items in a list. Rendering all the items at once can lead to slow performance and consume a significant amount of memory.
Windowing or List Virtualization addresses this problem by only rendering the portion of the items that is currently visible to the user on the screen. As the user scrolls through the list or window, the content is dynamically loaded and unloaded, ensuring that only the visible portion is actively present in the user interface.
There are various approaches and ways to implement list visualization. Some of the ways are by using libraries such as React Virtualized
, React Window
and React Infinite Scroller
In below example we are gonna see how list virtualization technique work with the help of React Virtualized
First we need to install the React Virtualized Package
npm install react-virtualized
In this example we will used the library to render a large list efficiently.We'll render a list of items containing numbers from 1 to 1000
.
import React from "react";
import { List } from "react-virtualized";
import "react-virtualized/styles.css";
const LargeList = () => {
const list = Array.from({ length: 1000 }, (_, index) => index + 1);
const rowRenderer = ({ index, key, style }) => {
return (
<div key={key} style={style}>
{list[index]}
</div>
);
};
return (
<div style={{ height: 400, width: 300 }}>
<List
width={300}
height={400}
rowCount={list.length}
rowHeight={30}
rowRenderer={rowRenderer}
/>
</div>
);
};
export default LargeList;
In the above example we imported the List
component from react-virtualized
along with the default styles.For haivng a large list we generated a list of items containing numbers from 1 to 1000 using Array.from
. The rowRenderer
function defines how each row should be rendered.
Then we pass the necessary props to the List
component
width
andheight
define the dimensions of the list container.rowCount
specifies the total number of items in the list.rowHeight
defines the height of each row in pixels.rowRenderer
is the function responsible for rendering each row.
Now when you render the LargeList
component, React Virtualized will efficiently render only the visible portion of the list, improving performance by avoiding unnecessary rendering during off-screen items.
In React applications, optimizing performance often involves managing the rendering of resources like images efficiently. When an application contains numerous images, rendering them all in the DOM nodes at once can significantly slow down the initial page load time.
To address this issue lazy loading is used to delay the loading of images until they are needed or visible to the user instead of loading all the images on page load.
Lazy loading images in React can be achieved in several ways and with the help of various libraries.
In below example we are gonna see how lazy load images technique work with the help of react-lazyload
First we need to install the react-lazyload
npm install react-lazyload
import React from "react";
import LazyLoad from "react-lazyload";
const LazyImage = ({ src, alt }) => {
return (
<LazyLoad height={200} once>
<img src={src} alt={alt} />
</LazyLoad>
);
};
const LazyImageLoaded = () => {
return (
<div>
<h1>Lazy Loaded Images</h1>
<LazyImage src="path_to_image" alt="Lazy Loaded Image" />
<LazyImage src="path_to_another_image" alt="Another Lazy Loaded Image" />
</div>
);
};
export default LazyImageLoaded;
First we define a functional component called LazyImage. We import the LazyLoad
component from react-lazyload
. Inside the LazyLoad component, we wrap an element. This element will be rendered lazily, meaning it will only load when it's about to enter the viewport.
By using this code, you can implement lazy loading of images in your React application, improving performance by only loading images when they're needed. Remember to replace "path_to_image" and "path_to_another_image" with the actual paths to your images.
Code splitting is another important optimization technique for a React application.. It involves breaking down the JavaScript code into smaller, more manageable chunks or bundles, and then loading only the required code for the current page or feature that the user is accessing.
When you develop a React Application, all your JavaScript code is typically bundled together into a single file. As the application grow the buundle also grows which results to slow intial load time for the users.
Code Splitting divides the single bundle into multiple chunks which can be loaded according to the needs to the application.
For example imagine you have a React application with multiple components, and you want to optimize its performance by splitting the code into smaller bundles. Let's say you have a simple application with two main components: HomePage
and AboutPage
.
import React from "react";
const HomePage = () => {
return (
<div>
<h1>Welcome to the Home Page!</h1>
</div>
);
};
export default HomePage;
import React from "react";
const AboutPage = () => {
return (
<div>
<h1>About Us</h1>
<p>This is the About Page.</p>
</div>
);
};
export default AboutPage;
Now we will create a main App component to dynamically load these components using code splitting:
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
const HomePage = lazy(() => import("./HomePage"));
const AboutPage = lazy(() => import("./AboutPage"));
const App = () => {
return (
<Router>
<div>
<h1>Application</h1>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route exact path="/about">
<AboutPage />
</Route>
</Switch>
</Suspense>
</div>
</Router>
);
};
export default App;
In the above App component we are using lazy
function provided by React to dynamically import the HomePage
and AboutPage
components. While using the lazy function the import()
is called only when the component is actually rendered, not during the inital load.
In the above example we have wrapped out dynamic imports with the Suspense component. The Suspense component allows us to specify a fallback UI while the component is being loaded.When the user navigates to the HomePage or AboutPage, the respective component is loaded on demand, reducing the initial bundle size and improving performance.
JavaScript is a single threaded application as it is designed to handle synchronous tasks. Web Worker is a JavaScript feature that allows you to run scripts in the background, independently of the main thread. This can be helpful for executing tasks that are computationally intensive or time-consuming without blocking the main thread, thus keeping the UI responsive.
In the below example we will see how we can use web worket in React and how it will help in optimization :
First we will create a separate JavaScript file called worker.js
for the Web Worker.This file will contain the code that you want to run in the background.
self.onmessage = function (e) {
const data = e.data;
const result = processData(data);
self.postMessage(result);
};
function processData(data) {
// Perform computationally intensive or time-consuming tasks here
// Return the result
}
In the above file the Web Worker listens for messages using the onmessage
event handler. When it receives a message it then processes the data and sends the result back to the main thread using the postMessage
method.
To use the web worker in React we can create a different instance of the Web Worker and communicate it using the Worker
constructor.
import React, { useState } from "react";
const MyComponent = () => {
const [result, setResult] = useState(null);
const handleButtonClick = () => {
const worker = new Worker("worker.js");
worker.onmessage = function (e) {
setResult(e.data);
worker.terminate();
};
const data = worker.postMessage(data); //Data to be processed
};
return (
<div>
<button onClick={handleButtonClick}>Process Data</button>
{result && <div>Result: {result}</div>}
</div>
);
};
export default MyComponent;
In the above React component, clicking the button triggers the creation of a new Web Worker instance. It then sends a message containing the data to be processed. Once the Web Worker completes its task and sends back the result, it updates the component state.
React Fragment
is a feature that allows you to group multiple element together without adding an additional DOM node.
Below is an example on how React Fragment
help in optimization.
When grouping and rendering multiple child elements we use <div>
to wrap them in a parent element
return (
<div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
</div>
);
In this case, a <div>
element is created as a parent for the three <p>
elements. However, if you don't actually need a parent element and only want to group the children for the sake of structure or semantics, you can use React.Fragment
:
return (
<React.Fragment>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
</React.Fragment>
);
while using React.Fragment
, we can avoid adding an unnecessary parent <div>
to the DOM, which can lead to a cleaner and more efficient DOM structure.