Performance boosters for UI applications

Shweta Singh
6 min readMay 18, 2020

I recently came across a problem in which my react application was taking unbearable time to load, leading to bad user experience. It was definitely violating the 3-seconds rule for any application. Therefore i started exploring some ways we can use to minimize the loading time.
Here in this post, we will try to understand how lazy loading and intersection observer can help to cut significant amount of loading time for our applications.

For example, I have created a sample react application, which does nothing but renders a page with a list of 101 cards, initially all collapsed. Each card has a title subtitle and card body has a chart inside as shown below.

Each card in the list of 101 cards
time taken to load screen initially

Tried to load the page which took around 8.5 seconds to load.

https://www.reactiongifs.us/wp-content/uploads/2015/12/loading_wait_doctor_who.gif

React Lazy Loading

Let’s see how lazy Loading affects this time? How can it help?

Have you noticed the bundle created by the Webpack by default in a CRA application? By default, Webpack 4 creates a bundle of your entire code and sends all your code as chunks each for your main code and one mainly with node modules.

Initial Chunks by default in CRA app

Now what if the bundle size becomes very large? 😫 You have imported multiple 3rd party libraries or have hundreds of components in your application code? It can take time to download also. That’s where, react lazy loading can come to the rescue and can give you the power to load only the essential files.

React lazy loading was introduced as a means of code splitting in version 16.6.

It lets you import the modules only when you want to render the component. i.e load the module and its dependencies only when needed.

We definitely don’t require the charting library to load unless we toggle to see a card body. Do we? Lets lazy load it with conditional rendering and see what changes? Find the gist here.

...
/**Graph will render only once show graph is true. i.e only on clicking toggle button */
{showGraph && (
<Suspense fallback={<div>Loading charts...</div>}>
<SampleChart id={id} /> </Suspense>
)}
...

If we expand the card and check the chunks. 💥 2 new chunk files added. Compare the size of the chunks above and now. (1.1 MB and 514 KB). But why?

Effect on file size

The chunk file (1.chunk.js) does not have the charting library or node module, therefore the size has reduced (from 1.1 MB to 514 KB). Also, only when you expand a card, the other two files get loaded which have the required code and dependencies.

Files after Lazy loading

Effect on loading time

Here, conditional rendering has reduced the time of loading significantly to merely 0.3 seconds. Since the chart has not rendered for the cards.

time after lazy load.

The only trade off is, once we open a card, a loader will be visible for the time chart takes to load.

Why to render things if they are not viewed or required at that moment ❓ ❓

Even though we have achieved a lot, but question comes? Why to render all the 100+ cards at once? Since we know user will be able to see only few at a time.

What if its not a card title? And may be some heavy element to be render as the card title or may be we want the list of cards to be expanded by default.

In that case, That’s where Intersection Observer chips in.

Intersection Observer

The Intersection Observer lets you detect the visibility of an element with respect to an element , or the viewport by default. Its an API exposed by the DOM to help you achieve what you want when the element has come into view using JavaScript. Options can be sent to the constructor to decide the threshold of the visible item, the element with respect to which we need to determine the intersection and many more.

The Intersection Observer can help you to achieve a lot. Infinite Scrolling or pagination can be achieved using this. It can be used if you want to add any animation to an element once its in view, for examples. Yes, we can achieve the auto-play the present video feature too. 😜

In our example lets not lazy load the chart and render only the visible cards, since i am adamant and don’t want to wait for the files to load and chart to render on toggling 😉 .

Lets see how i can achieve this in my code.

Using intersection observer to get inView to be true when element is visible

Note: I have used the react-intersection-observer library which helped me to use intersection observer easily in my app using all the methods (hooks, component etc).

Load the app now. The loading time has again come down to 0.3 seconds. It will give you an effect of infinite scrolling.. You can also render a placeholder to show unless the further cards are scrolled.

loading time With Only Intersection Observer

Imagine a dashboard where we have a number of charts and images, it can actually test our patience while loading 😫 . We can use Intersection Observer to render only the parts of the dashboard which is visible in the view port.

Yes, we can save the rendering time for the non visible charts and images but what about the code bundle to render it ❔ Hence we can make use of intersection observer to know when the element is visible and then react lazy loading to only load and render the module or image once its on the screen. It can boost our app’s performance to a great extent.

Let’s get back to our example and check the time if we use both in the code. The app loads within 0.2 seconds. 🍷 🍷

Loading time after using both lazy loading components and infinite scrolling

ℹ️ Using IntersectionObserver can lead to losing the ability to Ctrl-F with native browser functionality. Therefore, we should use it where its not crucial or necessary.

Here, this is just a small example for understanding purpose, which does not include even simple API call. Therefore, the time difference may seem to be negligible. But images and chart data can be large and expensive to render. Therefore, above two are some of the ways which can help to improve the app performance whether used together or individually.

Chunking in react can also include the SVG images and thus save you those extra calls for getting the images. You can read more about it here.

If required, example code can be found here. It includes use of both. Hope it will be useful in understanding.

Summary: Intersection Observer and Lazy loading can be great instruments individually and as well as together to improve the efficiency and performance of our application on UI side. Code splitting and lazy load can optimize our application code and help us to give a better user experience.

Thanks for reading. 🙏 Please feel free to comment. I’d love to hear from you.

References:
1. https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

2. https://reactjs.org/docs/code-splitting.html

3. https://css-tricks.com/a-few-functional-uses-for-intersection-observer-to-know-when-an-element-is-in-view/

--

--