Shub's logo

Improve Performance in JS and ReactJS

09 May, 2020

7 min read

This blog is a Work in Progress, and I am hoping to work on it part by part when I would have already worked on it once. Parts of the doc are complete as I have worked on it before and can assure, that they will surely benefit in Improving Performance.

⚠️  For now, read it at your own risk!!!

References to take a look at:

These guides were enough to boost my app performance.

Backpressure is something that is not exactly related to ReactJS and can occur to any JS app. To handle Backpressure, we have various ways, one of which I used in our Project.


React Profiling

Ok. So, if you are lazy and don't want to put too much effort on finding Performance glitches, Profiling is the best option.

React DevTools provides a developer with Profiler, which provides re-rendering data of components in Flame-Graph. This flame graph was developed by Brian Vaughn, creator of react-window, which he is using to virtualize the flame graphs as well.

By default, you do not need to import { unstable_Profiler as Profiler } from "react"; as of v16.4+ (I guess), Profiler is by default added to react development builds. If you do face that the Profiler in Devtools is not working, try importing the above Profiler Component, and wrap your Root Component with <Profiler />.

These Flame Graphs are easy to understand, and pin-points which Components are getting rendered unnecessarily.

Once you get to know which Components are getting re-rendered, due to some prop changes that shouldn't re-render them, you can use:

Above steps helps memoizing React Components, but that's not enough if you use Connected (or Container) Components. Vanilla Connected Components can have mapStateToProps that re-renders the whole component, whenever some state changes which is not even mapped to connected component.


For such cases, simplest solution is to use Selectors with Redux, specially ReSelect.

If you don't want to introduce ReSelect, and do things by yourself, then you need to create your own selectors and memoize them. Best and less complex library to memoize functions is memoize-one.


Combining both of the above solutions, will improve your app performance a lot. For me it gave a 50% increase in performance.

Browser Profiling

We are not limited to React DevTools Profiler, and I highly recommend to do Profiling in Browser DevTools as well, as it gives a broader insight on what is going on.

Details on How to read Profiling on Browser DevTools


Defer Rendering When Mounting & Unmounting Many Components

You can find a better explanation here


TLDR; Deferring a Render just means call any state updates behind requestAnimationFrame. What this will do is put the load off your Browser's main thread for sometime and allow the user to interact with UI without jankiness (or having an un-responsive UI).


Long lists need to have Virtualization or Pagination

Lists can be one of the bottlenecks in performance, if not done right. Most of the UI libraries out there are vanilla <ul /> lists.

Using 3rd-party Virtualization libs like react-window or react-virtualized could help improve performance as well as FPS of Scrolling.

You can get a very detailed explanation of what virtualization is, and what hurdles we need to resolve while building a Virtualized List, here @ Complexities of Infinite Scroller.

I think I will try to build a virtualized list in Rust,someday. Let's see!!! :bowtie:


Virtualization is difficult to integrate in Chat apps, where the list needs to be scrolled bottom-up and can have dynamic height for each list item.

So my implementation was to use a simple non virtualized list (relatively positioned list items). Most of the Chat apps in real world, like in Facebook or in Hangouts, all use the same principle. They paginate the chat with infinite scroll and use relatively positioned list.

Hope to create a separate blog, for Infinite Scrollable List using IntersectionObserver, which I worked on while working on Chat App at upGrad.

Infinite Scrolling (without Virtualization) is fine, till the time we don't see our list getting bombarded with too many DOM elements. In Chat, I haven't figured out a proper way to virtualize the list, but I am feeling that it will become very crucial when a Chat App is used by thousands of user at the same time, all bombarding messages in an instant populating chat messages in thousands, in a list which is not virtualized, the DOM will eat too much memory due to too many list items.

I will try react-window this time, for virtualization, but I have a feeling it won't be easy. One of the bugs I faced and thought was related to virtualization (but was not) is listed here, with a workaround solution.

react-window does not support dynamic item sizes, it's still a work in progress, but I do hope instead of focusing on supporting grids, and just working on Vertical Lists would be easier for me to work on, using the approach that is mentioned in the issue. ResizeObserver is something worth looking at, has OKayish support in major browsers.


Buffering Events that change state in less than an Animation Frame

I would suggest you to read this blog before going forward Handle Too many Socket events creating Backpressure, as it elaborates on basic concepts of Back Buffering.

TLDR; I am giving a small reference on how to implement buffering of events using rxjs.

rxjs.fromEvent(socket, 'message').pipe(
  () => source => source.pipe(
    rxjs.operators.buffer(source.pipe(waitForAnimationFrame()))
  ),
);

If say our Browser (or the Monitor in whole) supports only 60FPS, each requestAnimationFrame will run every 16ms, we can conclude:

Above code is buffering all incoming messages, for each requestAnimationFrame. What this solve is:

This handles back-pressure, by allowing only 60 re-renders per second, which our Browser can really handle, and not allow any unnecessary re-renders in-between.

Since, we are sure every 16ms our buffer will be emptied, we don't have to worry too much on memory overflows.


Lazy Loading Images:

I don't want to repeat things which are already done properly before, so to get details on Lazy Loading of Image, you can follow Addy Osmani's blog on the same, for better explanation. (To be honest I don't like writing that much, so saving myself some effort here as well 😅 😬)

One think I want to add here is that, if you do use Virtualized list, where each item contains images, they are lazy loaded anyways, so this attribute is not required for virtualized lists.

Preload Images


Some Side Notes:

TODOs:

I want to add more stuff here, regarding performance research which I am still exploring:

© Copyright 2020 Subroto Biswas

Share