DEV Community

Marko Rajević
Marko Rajević

Posted on

React performance - how to

In this post, we will go through some techniques and solutions to achieve good performances in your React application.

Dynamic import

Your app doesn't need to be one big bundle because you don't need all parts of your application immediately.

If you build a website with multi-pages you need the current page to be loaded immediately and other later when the user request those.

import { useState } from 'react';
import dynamic from 'next/dynamic';

const Modal = dynamic(() => import('../components/Modal'));

function Home() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(!showModal)}>Toggle modal</button>
      {showModal && <Modal />}
    </div>
  )
}

export default Home
Enter fullscreen mode Exit fullscreen mode

Next.js does this by default for you. It will create separate smaller bundles for each of your pages (routes).

Furthermore, you can dynamically load components and parts of the application which are not visible by default like modals or panels.

In the example above code for Modal will not be loaded until the component is rendered which means that your main bundle will be smaller and the initial page load faster.

If you are not using Next.js the same thing you can achieve with React.lazy.

React.memo

One thing that you don't want from your React app is unnecessary rerender 🙂.

If you wrap your component with React.memo you can ensure that your component will rerender only on props or state change, not whenever the component parent rerender.

React.memo compares prev and next props and if those are the same React will skip rendering the component, and reuse the last rendered result.
By default, props are compared shallowly but you can provide your custom comparison function as the second argument.

function MyComponent(props) {
  ...
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);
Enter fullscreen mode Exit fullscreen mode

When to use React.memo is up to you, my recommendation is to use it when you have a problem with the performance and rerenders of your component is too expensive.
Also, you can use it by default for the components with a lot of elements, like the lists or the tables.

How to properly use useCallback with React.memo you can check in my previous post here.

Profiler

Measure performances.

A great way to locate the components which are rerendering too many times or render slowly is to use Profiler HOC.
More about it you can read it here.

For the component you want to measure performances you need to wrap it with Profiler component.
Props that you need to pass to the Profiler are id and onRender.

return (
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Main {...props} />
  </App>
);
Enter fullscreen mode Exit fullscreen mode

Also, you can have multiple Profile components at the same time and you can nest them to measure the performances of different components within the same subtree.

onRender callback provides the next, very useful, informations.

function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

The most important information here is actualDuration, which shows how much time the component is needed for that current render.
Compare this time with baseDuration which is the time needed to render the component and entire subtree without memoization.

useMemo

This hook can help you if you create an object or an array within your component and that creation is time expensive.

It accepts two parameters. The first one is the function that returns the value you want to memoize and the second one is an array of dependencies.
If any of the dependencies change useMemo will recalculate the value, otherwise will return memoized value.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode

When to use it?
Well, I think that you can recognize operations that are expensive and can be memoized.
For example, if you have the map function within another map function and you are working with long arrays, that will be slow and it's good to be memoized.
Off course, you can measure how much time is needed for a specific operation and decide based on that.
For this purpose performance.now() can be used.

react-window

React is not very performant when it comes to rendering large lists or grids.

To resolve this problem plugins like react-window can be used.
The strategy is to only render the number of items that are in the viewport.

From the documentation:

  • It reduces the amount of work (and time) required to render the initial view and to process updates.
  • It reduces the memory footprint by avoiding the over-allocation of DOM nodes.
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>Row {index}</div>
);

const Example = () => (
  <List
    height={150}
    itemCount={1000}
    itemSize={35}
    width={300}
  >
    {Row}
  </List>
);
Enter fullscreen mode Exit fullscreen mode

 

Good practices

Debounce function.
It's not directly related to React but it can be applied.

If you call a function on an event that often occurs it's a good practice to debounce it.

You can use the debounce function from some library like Lodash or create your own.

function debounce(func, timeout = 250){
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}
Enter fullscreen mode Exit fullscreen mode

Now, for example, if you need to do something on window resize or scroll, it can be written like this:

useEffect(() => {
  const onResize = debounce(function() {
    // The function's code
  }, 250);
  const onScroll = debounce(function() {
    // The function's code
  }, 250);

  window.addEventListener('resize', onResize);
  window.addEventListener('scroll', onScroll);

  return () => {
    window.removeEventListener('resize', onResize);
    window.removeEventListener('scroll', onScroll);
  }
});
Enter fullscreen mode Exit fullscreen mode

 

Think how you organize your components.

For example, if you have this component:

const ItemsList = ({ items }) => {
  const [inputValue, setInputValue] = useState('');

  return (
    <div>
      <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
      {items.map((item) => {
        ...
      })}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The problem with this is that the entire component will rerender on every input change which is not optimal because besides the input there is the list of items as well which stays unchanged.

A better approach would be to move input out of the component and wrap the ItemsList component with React.memo so it can depend only on the items prop.

const ItemsList = React.memo(({ items }) => {
  return (
    <div>
      <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
      {items.map((item) => {
        ...
      })}
    </div>
  )
})

const ParentComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const [items, setItems] = useState([...]);

  return (
    <div>
      <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
      <ItemsList items={items} />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

 

That's all, have fun and create performant React apps. 😉

Top comments (0)