Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
December 27, 2020 03:45 am GMT

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;
Enter fullscreen mode Exit fullscreen mode
// Square.jsximport React from "react";import "./square.css";const Square = ({ onClick }) => {  return <div className="square" onClick={onClick} />;};export default Square;
Enter fullscreen mode Exit fullscreen mode

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.

Profile Screenshot showing 600ms spent for re-rendering

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);
Enter fullscreen mode Exit fullscreen mode

Now let's try to profile again with an additional setting as shown below.

Profile Screenshot

Profile Screenshot

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;
Enter fullscreen mode Exit fullscreen mode

Let's profile again. We are now avoiding re-rendering of Squares and this reduces the time to 118ms.

Profile Screenshot

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.

Alt Text

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.

Alt Text

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;
Enter fullscreen mode Exit fullscreen mode
// 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);
Enter fullscreen mode Exit fullscreen mode

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.

Profile Screenshot

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

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To