Optimizing React Applications for Maximum Performance
Suraj Vishwakarma

Suraj Vishwakarma @surajondev

About: Learning and helping other people to understand technology👨‍💻

Location:
Thane, India
Joined:
Jun 27, 2020

Optimizing React Applications for Maximum Performance

Publish Date: Aug 19 '24
207 23

Introduction

I have been writing React code for more than 3 years now. However, one thing that I didn’t focus on initially was optimizing the React performance. Most of the time the technical debt gets accumulated and it becomes challenging to optimize the performance.

It is quite hard to focus on optimization from the beginning but you can schedule for optimization from time to time to avoid huge technical debt.

We are going to look into some of the optimization techniques for React. This can be implemented while you write code. It is a matter of choosing this method over another method.

So, let’s get started.

1. Optimizing Large List

Rendering list is quite common as there are components in React. Rendering a large list is challenging because it can cause slow rendering and memory usage. Virtualization is the best way to handle such problems. It simply renders only visible lists and other items will be rendered when needed.

React Window and React Virtualized are popular libraries for the list of virtualization. They render only the items visible in the viewport, significantly reducing the number of DOM nodes rendered at any given time.

Here is an example with React Window:

    import { FixedSizeList as List } from 'react-window';

    const MyList = ({ items }) => (
      <List
        height={500} // Height of the container
        itemCount={items.length} // Total number of items
        itemSize={35} // Height of each item
        width={300} // Width of the container
      >
        {({ index, style }) => (
          <div style={style}>
            {items[index]}
          </div>
        )}
      </List>
    );
Enter fullscreen mode Exit fullscreen mode

2. useMemo

useMemo is a React hook that memorizes the result of a computation. Thus, it does not allow multiple processing of the calculation unless there are changes in dependencies. This can be useful for optimizing performance in scenarios where a function or calculation is expensive and shouldn't be re-executed on every render.

Syntax of the useMemo is:

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

As you can see, useMemo takes two arguments:

  • A function that returns a value so that it can be memorized.
  • An array of dependencies that determine when the memorized value should be recomputer.

Here is an example of useMemo:

    import React, { useState, useMemo } from 'react';

    const ExpensiveComponent = ({ a, b }) => {
      const computeExpensiveValue = (a, b) => {
        console.log('Computing expensive value...');
        return a + b;
      };

      const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

      return (
        <div>
          <p>Computed Value: {memoizedValue}</p>
        </div>
      );
    };

    const ParentComponent = () => {
      const [a, setA] = useState(1);
      const [b, setB] = useState(2);
      const [count, setCount] = useState(0);

      return (
        <div>
          <ExpensiveComponent a={a} b={b} />
          <button onClick={() => setCount(count + 1)}>Increment Count</button>
        </div>
      );
    };
Enter fullscreen mode Exit fullscreen mode

3. Code Splitting

In a traditional setup, all the components of your application are bundled into a single file. Code Splitting is an optimization technique for breaking down your application into smaller chunks. It reduces the application's loading time as you load smaller components and avoids other components that are not needed.

Here is an example of Code Splitting:

    import React, { useState } from 'react';

    function App() {
      const [component, setComponent] = useState(null);

      const loadComponent = async () => {
        const { default: LoadedComponent } = await import('./MyComponent');
        setComponent(<LoadedComponent />);
      };

      return (
        <div>
          <h1>Code Splitting Example</h1>
          <button onClick={loadComponent}>Load Component</button>
          {component}
        </div>
      );
    }

    export default App;
Enter fullscreen mode Exit fullscreen mode

4. React Lazy Load

React.Lazy is an important method for optimizing loading components. It enables you to lazy load components. This means that that component is only loaded if needed. Using this you can split your application into smaller components and loaded on demand.

React.lazy() is used to import a component dynamically. When the component is needed, it’s loaded asynchronously, and until then, a fallback UI (like a loading spinner) can be displayed.

Here is an example of Lazy Load:

    import React, { Suspense } from 'react';

    const LazyComponent = React.lazy(() => import('./MyComponent'));

    const App = () => {
      return (
        <div>
          <h1>My App</h1>
          <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
          </Suspense>
        </div>
      );
    };

    export default App;
Enter fullscreen mode Exit fullscreen mode

Throttling and Debouncing

It is not only specific to React but also general programming when calling a function. Throttling is a technique that defines the frequency at which a function is executed. When a function is throttled, it is only allowed to execute once within a specified time interval, regardless of how many times the event is triggered. For instance, adding throttling to a button click so that the button should not be invoked too frequently.

Example of Throttling:

    import React, { useState } from 'react';

    function ThrottledButton() {
      const [count, setCount] = useState(0);

      const throttle = (func, delay) => {
        let lastCall = 0;
        return () => {
          const now = new Date().getTime();
          if (now - lastCall >= delay) {
            lastCall = now;
            func();
          }
        };
      };

      const incrementCount = () => {
        setCount((prevCount) => prevCount + 1);
      };

      const throttledIncrement = throttle(incrementCount, 2000);

      return (
        <div>
          <h1>Count: {count}</h1>
          <button onClick={throttledIncrement}>Click Me</button>
        </div>
      );
    }

    export default ThrottledButton;
Enter fullscreen mode Exit fullscreen mode

Debouncing is used to ensure that a function should be executed after a certain period of time after invoking the function. When an event occurs repeatedly, the debounced function will only execute after the event has stopped firing for the specified delay period. For instance, when the user is typing in the search input and for providing suggestions we wait a few milliseconds before invoking the function so that the user completes the typing.

Example of Debouncing:

    import React, { useState } from 'react';

    function debounce(func, delay) {
      let timeoutId;
      return function (...args) {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
          func(...args);
        }, delay);
      };
    }

    const DebouncedSearch = () => {
      const [query, setQuery] = useState('');

      const handleSearch = (event) => {
        setQuery(event.target.value);
        console.log('Searching for:', event.target.value);
        // Here you would typically trigger an API call or filter a list based on the query
      };

      const debouncedSearch = debounce(handleSearch, 500);

      return (
        <div>
          <h1>Search</h1>
          <input
            type="text"
            placeholder="Type to search..."
            onChange={debouncedSearch}
          />
          <p>Search Query: {query}</p>
        </div>
      );
    };

    export default DebouncedSearch;
Enter fullscreen mode Exit fullscreen mode

Connect With Me

Let's connect and stay informed on all things tech, innovation, and beyond! 🚀

Also, I am open to writing freelance articles if you are interested then contact me over email or social.

Conclusion

Optimizing React applications is crucial for ensuring they run smoothly and efficiently, especially as they grow in complexity and size. By incorporating techniques like list virtualization, memoization with useMemo, code splitting, lazy loading, throttling, and debouncing, you can significantly enhance the performance of your React applications.

I hope this method will be beneficial in optimizing the performance of your React application. Thanks for reading the article.

Comments 23 total

  • Boopathi
    BoopathiAug 20, 2024

    short and crisp

  • Russ Painter
    Russ PainterAug 20, 2024

    Great stuff. Can anyone recommend a library for throttle/debounce? In the vue world we have VueUse for this sort of thing.

    • Suraj Vishwakarma
      Suraj VishwakarmaAug 20, 2024

      You can look into the JS utility library such as Lodash and Underscore.

    • Ikem O
      Ikem OAug 20, 2024

      You can check out "use-debounce" library too

    • Red Ochsenbein (he/him)
      Red Ochsenbein (he/him)Aug 20, 2024

      Write it yourselves. It's really not that hard.

    • Joshua Raffel
      Joshua RaffelAug 20, 2024

      Have a look on this library: usehooks-ts.com/
      It supports many useful react hooks including debounce.

    • José Pablo Ramírez Vargas
      José Pablo Ramírez VargasAug 20, 2024

      Debouncing is the simplest thing to do. Just code it.

      export function debounce(fn, timeout) {
          timeout = timeout ?? 500;
          const timeoutId = setTimeout(fn, timeout);
          return {
              canccel() {
                  clearTimeout(timeoutId);
              }
          };
      };
      
      Enter fullscreen mode Exit fullscreen mode

      Or am I missing something?

      • Joao Polo
        Joao PoloAug 21, 2024

        debounce + react async updates (useState, etc) + async fetch might be a bit more complex.

    • CitronBrick
      CitronBrickAug 21, 2024

      rxjs can do that. It used to be bundled with Angular. Not sure about now.
      rxjs.dev/api/index/function/debounce
      rxjs.dev/api/index/function/throttle

      • Joao Polo
        Joao PoloAug 21, 2024

        used rxjs on react and it's awesome. I'm not using for now, but I liked it.

    • Joao Polo
      Joao PoloAug 21, 2024

      I use aHooks and it really matter, because you can define what you need to execute on throttle or debounce. Also this lib can deal with loading status, auto refresh, etc.

  • Sachin Gadekar
    Sachin GadekarAug 20, 2024

    more informative

  • Rushikesh Suryawanshi
    Rushikesh SuryawanshiAug 20, 2024

    React high order components could also optimise your code

  • Antonio Rodríguez
    Antonio RodríguezAug 20, 2024

    Great post.

  • Martin Baun
    Martin BaunAug 20, 2024

    Great insights! Callstack has an excellent guide to React optimization as well :)

  • KAMAL raj
    KAMAL rajAug 20, 2024

    Very informative 👏

  • 케빈
    케빈Aug 21, 2024

    nice

  • Madza
    MadzaAug 21, 2024

    This is awesome, thanks for sharing mate!

  • mohamed karim
    mohamed karimAug 22, 2024

    Thank for sharing

  • Mannega
    MannegaOct 20, 2024

    I don't even know how to use React (yet) what am I even doing ? Anyway it was such a helpful post 👏

Add comment