CHAPTER #4

How to Deal With Common React JS Problems – Actionable Tips

Actionable tips for product teams that will help you solve React JS problems with performance, app loading time, and SEO.

Last Update

16 Nov, 2020

Reading Time

10 Min

Authors
Piotr SuwałaFull Stack Developer
Github LinkLinkedin Link
Bianka PluszczewskaEditor
Linkedin Link

Introduction

Since its release in 2013, React JS, together with a few of its libraries, grew to a solid and stable JavaScript framework. 

As the statistics suggest React is often favored by frontend developers and technical leaders due to its ease of use and logical breakdown into components.

Yet, although React is easy to use when building complex frontend applications, this ease comes with a cost if the frontend is written carelessly.

In this article, we are going to focus on how to deal with common React JS problems tech leaders meet, such as:

  • big lists of data, 
  • useless renders, 
  • search engine optimization (SEO),
  • decomposition of the logical and UI layers.

React JS problems with performance

React itself boasts about its high performance, which is true, yet, there are cases where careful actions need to be taken.

The concept of virtual DOM

Before we start discussing how to deal with React performance problems we need to first understand how React UI is rendered. 

The key concepts here are DOM and virtual DOM. 

DOM” stands for Document Object Model which is the structure of a webpage composed of a tree of objects. In layman’s terms, it means the HTML elements of the webpage. 

Virtual DOM” is the representation of the DOM kept in the memory and synchronized with the real DOM when it needs to be updated. Thanks to this approach developers are able to abstract out the attribute manipulation, event handling, and manual DOM updating.

The image shows that the virtual DOM (kept in memory) computes the difference when a state change occurs, and only then it is applied to the real DOM.

Long lists of items

A common repeating performance problem in React JS is a complex and long list of items.

Say the app needs to render many cards (in some cases, even hundreds) which are composed of images (or even better – carousels of images), titles, links, ratings and they need to be responsive. 

The slowdown on low-spec laptops and mobile devices may be noticed very quickly. 

This happens because React needs to observe each change in every element of the list. This process consumes lots of resources. 

Luckily, it isn’t that difficult to fix – virtualized lists come to the rescue. 

Key take-away: long list of items can result in poor performance on low-spec devices. To prevent the issue use virtualization.

A virtualized list is a list that renders only the content visible on the screen and blanks out all other elements until the user decides to scroll down or up in order to see them, thus, saving lots of valuable resources. 

The most popular library for the list virtualization is react-window.

As seen in the graphic above, only the items that can be currently viewed on the screen are rendered. The rest is blanked out.

Wasted rerenders

Wasted renders are one of the most common React JS problems. 

What’s causing them?

To understand it, we need to first comprehend how data is passed in React. 

There are many ways, but one of the simplest is through “props” (data passed directly to the component).

Say there’s an element such as “Item” which may represent a single item on the list. If there is a list of two items and developers want to pass a value that is computed in the list to each one of the cards, they can do it this way:

See how to deal with React JS problems, such as wasteful renderers.

The problem here is that each time “getNewValue” is called, each item of the list is rerendered, which is a waste of resources as the value is always the same. 

This example here is a dummy one in order for the example not to look too complicated. 

In real life, developers could deal with complex data structures being rerendered which visibly slow down the performance, e.g. messages in a group chat – a large collection of elements that contain text, images, and quotes of other messages.

Do end-users notice “wasted renders” or maybe is it simply a flaw for developers?

The answer is: this is often a problem for the end-user as well. 

The end-user will notice:

  • a waste of CPU, as the processing unit needs to recalculate the same value(s), thus, resulting in faster battery drainage,
  • slow down of the application if the rerenders happen often (dozens of times per minute) and require repeatable heavy computations – often perceived by users as “app lagging”.

A tool that greatly eases the pain of manually checking every rerender is “Why did you render”.

How can developers use it?

By launching it in the app initialization and adding a whyDidYouRender static to the components they want to track, just like this:


class BigListPureComponent extends React.PureComponent {
static whyDidYouRender = true
// a lot of logic and handlers
  render(){
    return (
      // a list of 1000000000000 entries
    )
  }
}

If anything goes wrong a notification will pop up in the console:

The fix for that is easy to implement, however, one needs to be careful not to eliminate rerenders when they really need to happen.

Developers can use “PureComponent” which performs a shallow comparison of arguments passed to a component with the previous values and then decides if the component needs to rerender. 

Quite often, however, this is not enough and developers need to manually tell the components if they need to rerender by providing custom comparison functions. 

If the components are arranged in a way that observable data is set at the very top of the screen’s hierarchy component then this practice may accidentally cause a wasted rerender which happens each time a character is added to or deleted from the input field that is located on the screen and connected to a data provider such as Redux.

PureComponent or a React’s new feature of “React.memo” lets developers prevent such situations.

Key take-away: to improve the app’s performance and avoid unnecessary rerenders use a detection tool called “Why did you render” and implement necessary fixes. Try “PureComponent”, a tool that determines if the component needs a rerender and either lets it pass, or stops it.

At the end of the day, React only solves the UI problems

Teams should keep one thing in mind – React alone is only a UI library which is rarely enough and is never sufficient when the application grows complex.

Complex projects often need a toolkit that could handle the app’s logic and effectively manage the current state. Using React JS alone for that purpose will only result in a bloated codebase that will become hard to manage. In such cases, libraries like Redux or MobX might prove useful.

High loading times in complex React applications

Before you consider choosing React for the project, you should know that adding React atop an existing application won’t make it faster or lighter. It’s quite the opposite. 

However, the expected results can be achieved by replacing part of the frontend codebase with React.

Code splitting

React apps can be fast if they are thoughtfully designed. Yet, sometimes good intentions are not enough and the app ends up being slow.

Typically, the JavaScript bundle comes to the point where it becomes heavy and takes more time to download and parse correctly. 

However, this is rarely the case the whole JavaScript code needs to be used on a single page, to the contrary, usually a small portion is sufficient. 

Fortunately, modern JavaScript bundlers (such as Webpack or Parcel) have it covered and they let developers split the code into a few separate bundles so that page /a ideally loads the content only needed for /a while page /b loads only the content required for page /b thus drastically decreasing the bundle size and loading time.

Key take-away: if you want to improve the apps’ performance try implementing code-splitting, which allows only the parts necessary for the specific view to load.

To give a better real-life example: can you imagine launching Facebook on the feed board and getting all the code required not only for the feed but also for managing groups, creating new chats, or scrolling up to see old messages? 

That would drastically increase the size of Facebook web client causing the app to load much slower.

Another example is Netflix. 

Do you need the code for launching your favorite shows on the login or landing page? 

The very first “paint” of the web page should take as little time as possible so that the user stays on the site. At this point, downloading a major portion of the whole Netflix code is a big no-no.

Large third party modules

On some projects, usually, late in the development process, developers start to notice problems with third-party modules caused by their size. 

Why does that happen?

It usually starts innocently. Teams want to simplify the building process, add a plugin here and there and it soon turns out that the app weighs so much that users cannot access the app in a reasonable time. 

This has been a huge problem for moment.js which is now officially deprecated and unsuitable for any new application and should be replaced with a modern (lightweight) module in existing apps.

If developers are unsure whether a third-party module causes slowdowns in the app they might want to check Webpack Bundle Analyzer, a tool that visualizes the size of webpack output files with an interactive zoomable treemap.

This module will help:

  • see what really is inside the bundle,
  • find out which modules make up the most of its size,
  • find modules that were included in the bundle by mistake,
  • optimize bundle’s size.

Gzip compression

Some teams may be interested in Gzip compression. This is a technique of compressing files so that they weigh less and are faster for the end-user to download. Further reading here.

Server-side rendering

What definitely boosts the loading time of React apps is a process called server-side rendering (SSR). 

JavaScript apps are client-side applications, meaning the whole code needs to be first downloaded, parsed, then it needs to build the DOM, and only then it is usable. 

The SSR approach allows developers to generate HTML on the server so that the first request returns a visible website quickly, and soon after the JavaScript bundle is downloaded it “hydrates” itself which means it reuses the markup generated on the server. 

Any server-side language can generate HTML out of existing React codebase and there are tools that were created specifically for this purpose. 

Key take-away: thanks to server-side rendering the app is able to return a visible website with the first request.

One of the widely recommended solutions for React apps is GatsbyJS

Gatsby is a React-based open-source framework for creating websites and apps. However, it won’t work out of the box – it will most likely require mocking certain values that depend heavily on the end-user’s environment such as “visible area offset” which is the offset used for virtualized lists. 

Note that the application will not be usable for some time after the HTML (UI) is visible to the user. Typically the user will need to wait at least a second for the page to be interactive which in most cases shouldn’t be an obstacle.

Search engine indexing problems

Web client-side based applications have a problem. Typical React JS apps which are SPAs (single page applications) may look beautiful, work smoothly, and behave in the same way as native apps but they are not super SEO-friendly. 

This happens because a search engine (such as Google or DuckDuckGo) sees only HTML tags and a few scripts that it is supposed to load. 

Modern search engines load the code and are able to crawl the website but it is still a slow process

Fortunately, this again is when the aforementioned SSR comes into action. If developers prerender the HTML on the server then the crawling bot has everything it needs to properly index the site.

Want SEO-friendliness? Don’t use React alone

When only React is used on the frontend search engines see just one page for everything – which is bad for the SEO.

As mentioned earlier React itself is rarely used alone. 

If more than just one page is needed, the developers will have to build a custom solution or use an acknowledged and battle-tested one such as React Router. This tool lets developers create separate pages so that search engines can see them.

React apps often use “react-helmet” which lets developers easily set up SEO headers such as page title or OpenGraph. It supports SSR so it’s even easier to use.

Example in a React snippet:

const App = () => (
Here's the Title!

My Amazing React SEO Page

Hello World!

)

Decomposing React UI

What often happens to React components is that sooner or later they become big fat chunks hard to read and understand. 

When this happens, it is always advised to split the big component into a few smaller so they are easier to understand. 

This solution has an additional business advantage – the smaller components can be reused elsewhere.

One of the recommended ways of splitting React components suggests dividing them into: 

  • a file that contains only the UI part – the primary building block with as little logic as possible,
  • a container file that contains functions, connection to Redux or other tools for management of the state of the application,
  • a file that contains styles (e.g. styled-components).

This way developers will be able to split a component into unit-testable parts and readability and complexity will drop significantly. Furthermore, it is then easier to replace a piece of logic without going through the whole file looking for references of the previous code.

In general, keeping complex logic inside React JS components should be avoided. 

Key take-away: avoid keeping complex logic inside the components. It’s better to split one big component into a few smaller that are easy to read and unit-test.

Developers should either come with a custom solution or use an existing one such as Redux-toolkit or ReduxSaga so that Views only or mostly receive data without any or little side effects.

How to prevent React JS problems – Tips

Although React is usually considered a well-performing technology if not used carefully it too can cause some problems.

When developing React apps pay attention to good practices from the start and try to identify potential stumble blocks ahead of time.

This approach will help your team to save time at a later point and will prevent users from experiencing problems.

To prevent the most common React app problems:

  1. Consider if server-side rendering would be beneficial in your particular case.
  2. Perform bundle splitting with Webpack or another JavaScript bundler where applicable.
  3. Decompose React UIs to make them easier to read and test.
  4. Remember that you can make your web app faster not by adding but rather by replacing the existing piece of code with React.
  5. Take a look at which components rerender often, see how often it is unnecessary, use a comparison function to decide which rerenders are needed.
  6. Use virtualized lists to improve performance on low-spec devices.