An Interest In:
Web News this Week
- April 20, 2024
- April 19, 2024
- April 18, 2024
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
React Performance Optimization Tips
In this post, we will look into how we can improve the performance of React apps that need to render a lot of components on the screen.
We generally consider using pagination
or virtualization
for most of our apps to provide a better user experience and that works for most of the use cases, but what if we have a use case where we need to render a lot of components on the screen while not giving up on the user experience and performance.
For the demonstration, I have considered a simple app that renders 10k squares on the screen, and we update the count when the squares are clicked. I am using react 17.0.0
and functional components with hooks.
Here is the preview of the app. It has an App
component and a Square
component. There is a noticeable lag on clicking the squares.
Stackblitz Preview
Stackblitz Editor
// App.jsximport React, { useState } from "react";import Card from "./components/square/square";const data = Array(10000) .fill() .map((val, index) => { return { id: index, key: `square-${index}` }; });const App = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> {data.map(({ key }) => ( <Card key={key} onClick={() => { setCount(val => val + 1); }} /> ))} </div> );};export default App;
// Square.jsximport React from "react";import "./square.css";const Square = ({ onClick }) => { return <div className="square" onClick={onClick} />;};export default Square;
Let's add console statements to both the components to check if they are rendering unnecessarily and then click on one of the squares. We see the Square
component function is getting called 10k times.
Also, we can see that 600ms
are spent in re-rendering the UI on React Dev tools Profiler Tab. Start the profiling on page load -> click any square -> stop profiling.
We need to avoid re-rendering of Square
component as none of the props
for it is changing. We will use React.memo
for this.
What is React.memo
?
React.memo
is a higher order component that helps to skip the re-rendering by memoizing the result of the initial render. React.memo
re-renders component only when the prop
changes.
NOTE:
React.memo
does a shallow comparison. For more control, we can pass a comparison function as below.React.memo(Component, (prevProps, nextProps) => { // return true if the props are same, this will skip re-render // return false if the props have changed, will re-render });
Here is the Square
component with React.memo
// Square component with React.memoimport React from "react";import "./square.css";const Square = ({ onClick }) => { return <div className="square" onClick={onClick} />;};export default React.memo(Square);
Now let's try to profile again with an additional setting as shown below.
We don't see any difference yet. But when we hover on the Square
component it shows onClick
prop has changed which has triggered this re-render. This happens as we are passing a new function during each render for the onClick
prop. To avoid this we use useCallback
.
What is useCallback
?
useCallback
is a hook that returns a memoized callback.
// App component with useCallbackconst App = () => { const [count, setCount] = useState(0); const onClick = useCallback(() => { setCount(val => val + 1); }, []); return ( <div> <p>Count: {count}</p> {data.map(({ key }) => ( <Card key={key} onClick={onClick} /> ))} </div> );};export default App;
Let's profile again. We are now avoiding re-rendering of Squares
and this reduces the time to 118ms
.
We see much better performance now. We are avoiding the re-rendering of Square
components using memoization but React
still needs to compare the props for all the 10k elements. Here is the component tree for our app.
Going one step further. We have 10k Square
elements below the App
component. To reduce the time React takes to compare props we need to reduce the components at this level. What can be done here? Can we introduce another layer of components? Yes, we will be splitting the list of 10k items into smaller chunks and render those by using an intermediate component.
In a real-world app, we can find a logical place to split the list into smaller chunks. But here let's split them into chunks of 500 Squares each.
// App componentimport React, { useState, useCallback } from "react";import Row from "./components/row/row";const data = Array(10000) .fill() .map((val, index) => { return { id: index, key: `square-${index}` }; });const chunkArray = (array, chunkSize) => { const results = []; let index = 1; while (array.length) { results.push({ items: array.splice(0, chunkSize), key: String(index) }); index++; } return results;};const allItems = chunkArray(data, 500);const App = () => { const [count, setCount] = useState(0); const onClick = useCallback(() => { setCount(val => val + 1); }, []); return ( <div> <p>Count: {count}</p> {allItems.map(({ items, key }) => ( <Row items={items} onClick={onClick} key={key} /> ))} </div> );};export default App;
// Row componentimport React from "react";import Square from "../square/square";const Row = ({ items, onClick }) => { return ( <> {items.map(({ id, key }) => ( <Square key={key} onClick={onClick} id={id} /> ))} </> );};export default React.memo(Row);
Let's profile again. We do not see any lag now. We have a lot fewer Row
components so the prop comparison is pretty quick also React can skip Square
prop comparison if the Row
props have not changed.
Here is the final app
Stackblitz Preview
Stackblitz Editor
React.memo
and useCallback
can be used to get better performance. Does it mean we should wrap all components with React.memo
and all functions with useCallback
? No. React.memo
and useCallback
use memoization which adds up to the memory, also the functions themselves take time to run and have overheads like the prop comparison. The splitting that we have done adds up to the memory as well.
When to use React.memo
and useCallback
?
They are not required unless you see some lag in a specific component or the complete app. If there is a lag try profiling for the actions on that screen and check if there can be any component re-renders that can be avoided.
Conclusion
While React.memo
, useCallback
, useMemo
can be used to optimize the performance of the React apps they are not required in most cases. Use them Cautiously.
Original Link: https://dev.to/harshdand/react-performance-optimization-tips-4238
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To