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.
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.
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 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 offers a compelling solution by deferring the loading of components until they're truly needed. Here's how you can implement it for Popups:
React.lazy
and Suspense
from React.const LazyPopup = lazy(() => import('./ABStanddardPopup'));
<Suspense fallback={<Loader active inline />}>
<LazyPopup ... />
</Suspense>
Benefits of Lazy Loading Popups:
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!