While developing or expanding a React app you may experience problems with performance, app loading time, and SEO. Fortunately, they can be easily solved. A few actionable tips shared by skilled React developers will help you.
A QUICK SUMMARY – FOR THE BUSY ONES
Problem: Rendering complex, lengthy lists can slow down low-spec devices due to resource consumption.
Mitigation: Use virtualized lists that render only visible content, saving resources. Popular libraries include react-window. Virtualization enhances performance by rendering only what's needed.
Problem: Unnecessary rerenders occur when data is passed through props, leading to redundant computations and performance degradation.
Mitigation: Utilize PureComponent or React.memo to prevent unnecessary rerenders. Implement custom comparison functions for precise control. The "Why did you render" tool aids in identifying and rectifying wasted rerenders.
Problem: React primarily addresses UI concerns and may not suffice for complex projects requiring advanced state management and logic.
Mitigation: Integrate state management libraries like Redux or MobX to handle application logic efficiently. Using React in conjunction with these tools prevents codebase bloat and facilitates better code organization.
Problem: Navigational performance can impact user experience.
Mitigation: Choosing appropriate navigation libraries like React Navigation ensures smooth navigation transitions.
Problem: Slow app launch due to excessive dependencies. Large app size affects performance negatively.
Mitigation: Reduce app size by minimizing dependencies, compressing images, and optimizing resources. Prioritize fast, high-performance components.
Problem: Unnecessary background processes in Android apps may cause memory leakage.
Mitigation: Implement scrolling lists (SectionList, FlatList, VirtualList) to efficiently manage memory. Proper resource handling prevents memory leaks.
Problem: Outdated or insecure dependencies can compromise app security.
Mitigation: Regularly update dependencies, use security testing tools, and adhere to best practices. Tools like npm audit help identify and resolve vulnerabilities.
Problem: Improper handling of user inputs can lead to XSS attacks.
Mitigation: Sanitize user inputs, use JSX for rendering, and apply Content Security Policy (CSP) headers. Employ security libraries to prevent XSS vulnerabilities.
Problem: Storing sensitive data improperly exposes it to potential attacks.
Mitigation: Avoid storing sensitive data in client-side storage. Use secure storage mechanisms and encryption to protect sensitive information.
TABLE OF CONTENTS
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:
React itself boasts about its high performance, which is true, yet, there are cases where careful actions need to be taken.
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.
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.
<span class="colorbox1" fs-test-element="box1"><p>Key take-away: long list of items can result in poor performance on low-spec devices. To prevent the issue use virtualization.</p></span>
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 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:
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 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.
<span class="colorbox1" fs-test-element="box1"><p>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.</p></span>
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.
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.
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.
<span class="colorbox1" fs-test-element="box1"><p>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.</p></span>
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.
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:
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.
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.
<span class="colorbox1" fs-test-element="box1"><p>Key take-away: thanks to server-side rendering the app is able to return a visible website with the first request.</p></span>
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.
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.
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:
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:
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.
<span class="colorbox1" fs-test-element="box1"><p>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.</p></span>
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.
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.
<span class="colorbox1" fs-test-element="box1"><h3>To prevent the most common React app problems:</h3><ol><li>Consider if server-side rendering would be beneficial in your particular case.</li><li>Perform bundle splitting with Webpack or another JavaScript bundler where applicable.</li><li>Decompose React UIs to make them easier to read and test.</li><li>Remember that you can make your web app faster not by adding but rather by replacing the existing piece of code with React.</li><li>Take a look at which components rerender often, see how often it is unnecessary, use a comparison function to decide which rerenders are needed.</li><li>Use virtualized lists to improve performance on low-spec devices.</li></ol></span>
Our promise
Every year, Brainhub helps 750,000+ founders, leaders and software engineers make smart tech decisions. We earn that trust by openly sharing our insights based on practical software engineering experience.
Authors
Read next
Popular this month