Optimize React Application Performance with Lazy Loading for Semantic UI Popups
Adam C. |

Are you experiencing memory issues or performance bottlenecks in your React application, particularly when dealing with a multitude of Popup components from Semantic UI? Fear not! Lazy loading can come to your rescue. In this post, we'll explore how lazy loading can alleviate memory concerns and streamline the user experience, especially with memory-intensive components like Popups.

Photo by Glen Noble on Unsplash

Update (1/18/2024)

I changed the solution to use a dynamic component since my project is using Next.JS.  The main reason is that:

When you use dynamic imports with React.lazy and Suspense, the code within the dynamically imported component may still be executed during the initial server-side rendering. This is because the server needs to render the complete React tree, including the dynamically imported components, to generate the initial HTML that is sent to the client.

So, even though the actual JavaScript for the dynamically imported component is not loaded on the server side, the component is still executed during server-side rendering. 

The Memory Woes

In a real-world scenario, loading numerous Popups on a page, particularly when using Semantic UI, can lead to occasional failures with errors like:

2024-01-16T23:43:28: RangeError: Map maximum size exceeded
2024-01-16T23:43:28:     at Map.set (<anonymous>)
2024-01-16T23:43:28:     at MapCache.mapCacheSet [as set] (…/next/node_modules/lodash/_mapCacheSet.js:17:8)
2024-01-16T23:43:28:     at memoized (…/next/node_modules/lodash/memoize.js:63:28)
2024-01-16T23:43:28:     at Popup.render (…/next/node_modules/semantic-ui-react/dist/commonjs/modules/Popup/Popup.js:328:62)

After a thorough investigation, it was discovered that Popup uses lodash.memoize to maintain the same reference between renders. As what they commented at https://github.com/Semantic-Org/Semantic-UI-React/blob/ef8684ca969f30a2cb6d87c37fd09ac84ef05267/src/modules/Popup/lib/createReferenceProxy.js

/** 
 * Popper.js does not support ref objects from `createRef()` as referenceElement.
 * If we pass directly `ref`, `ref.current` will be `null` at the render process.
 * We use memoize to keep the same reference between renders.
 * 
 * @see https://popper.js.org/popper-documentation.html#referenceObject
 */
const createReferenceProxy = _.memoize(
  (reference) => new ReferenceProxy(isRefObject(reference) ? reference : { current: reference }),
);

This raised concerns: Does it mean that if we call <Popup> millions of times, it will eventually exceed the size in lodash.memoize?

Here are answers found by Bard AI:


Yes, theoretically, calling Popup millions of times could eventually exceed the default size limits within Lodash's memoize cache.

 

However, several factors mitigate this risk:

  • Unique References: The cache keys are based on distinct reference objects, so only unique references will be stored.
  • Garbage Collection: JavaScript's garbage collection can reclaim memory from cached values that are no longer referenced.
  • Default Size: While Lodash's default cache size is not specified, it's typically large enough for most use cases.

Memoization and Memory Growth:

Memoization is a performance optimization strategy that caches results of function calls, preventing redundant computations. While beneficial, it can contribute to memory growth if not managed carefully. In the context of Popups, memoization might lead to cache expansion as more instances are created and used.

Lazy Loading to the Rescue:

Lazy loading offers a compelling solution by deferring the loading of components until they're truly needed. Here's how you can implement it for Popups:

  1. Import React.lazy and Suspense from React.
  2. Create a lazy component for your Popup:
  3. Render the lazy component conditionally, using Suspense for a loading indicator:
const LazyPopup = lazy(() => import('./ABStanddardPopup'));

<Suspense fallback={<Loader active inline />}>
  <LazyPopup ... />
</Suspense>

Benefits of Lazy Loading Popups:

  • Reduced Initial Page Load: Popups are only loaded when triggered, improving initial load time and perceived performance.
  • Optimized Memory Usage: Popups and their associated memoization caches reside on the client side, reducing server-side load and optimizing memory usage.
  • Enhanced User Experience: Delayed loading of Popups prevents unnecessary resource consumption and potential UI freezes, leading to a smoother user experience.

Conclusion:

Lazy loading is a valuable tool for optimizing React applications, particularly when dealing with memory-intensive components like Popups. By embracing lazy loading, you can achieve faster initial load times, enhanced performance, and a more seamless user experience. Embrace lazy loading and watch your Popups soar without memory constraints!