[SURVEY RESULTS] The 2024 edition of State of Software Modernization market report is published!
GET IT here

How to Deal With Common React JS Problems - Actionable Tips

readtime
Last updated on
September 18, 2023

A QUICK SUMMARY – FOR THE BUSY ONES

Limitations of React with mitigation tips

Long lists of items

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.

Too many rerenders

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.

React alone for UI

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.

Navigation issues

Problem: Navigational performance can impact user experience.

Mitigation: Choosing appropriate navigation libraries like React Navigation ensures smooth navigation transitions.

Launch time optimization

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.

Memory leakage

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.

Insecure dependencies

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.

Cross-site scripting (XSS) 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.

Insecure data storage

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.

Key points for improving performance in complex React applications

  1. Adding React to an existing application doesn't automatically make it faster. Careful replacement of parts of the frontend codebase with React is more effective.
  2. Divide the JavaScript bundle into separate bundles using code splitting.
  3. Load only the necessary code for each page, reducing bundle size and loading time.
  4. Avoid using heavy third-party modules that negatively impact app size.
  5. Utilize tools like Webpack Bundle Analyzer to identify large modules and optimize bundle size.
  6. Compress files using Gzip to reduce download time and improve performance.
  7. Implement SSR to generate HTML on the server for faster initial rendering.
  8. Use tools like GatsbyJS for SSR, enhancing performance and SEO.
  9. React SPAs may not be SEO-friendly due to delayed crawling of scripts.
  10. Implement SSR for proper search engine indexing and improved SEO.
  11. Split large React components into smaller, reusable parts for improved readability and maintainability.
  12. Divide components into UI, container, and styles files for better organization.
  13. Avoid complex logic within React components.
  14. Use state management libraries like Redux and Redux Saga to handle logic and side effects separately from UI components.
  15. Use React-Router to create separate pages for SEO-friendly applications.
  16. Employ React-Helmet for managing SEO headers and support SSR.

TABLE OF CONTENTS

How to Deal With Common React JS Problems - Actionable Tips

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,
  • potential security vulnerabilities.

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.

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

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. 

<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.

See how to deal with React JS problems, such as long lists of items.

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 - too many rerender

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:

See how to deal with React JS problems, such as wasted renderers.
Image source

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>

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.

<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.

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. 

<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.

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.

See how to deal with React JS problems, such as SEO indexing.
Visual representation of server-side rendering

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:

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. 

<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.

React security vulnerabilities

Cross-Site Scripting (XSS)

  • React applications are susceptible to XSS attacks if user-generated content is not properly sanitized or escaped.
  • Use libraries like DOMPurify to sanitize user input before rendering it to prevent malicious script execution.
  • Utilize React's built-in features like JSX to ensure that user inputs are treated as data, not as executable code.

Mitigation

  • Sanitize user inputs using libraries like DOMPurify before rendering them.
  • Use React's built-in features like JSX to prevent script injection.
  • Set Content Security Policy (CSP) headers to restrict the sources from which scripts can be loaded.

Injection attacks

  • Improper usage of dynamic data in JSX expressions can lead to injection vulnerabilities.
  • Avoid directly interpolating user inputs into JSX expressions; use component props or state variables instead.
  • Utilize libraries like serialize-javascript to safely serialize data for embedding in scripts.

Mitigation

  • Avoid interpolating user inputs directly into JSX expressions; use component props or state variables.
  • Use serialize-javascript to safely serialize data for embedding in scripts.

Insecure dependencies

  • Third-party packages used in a React project might have vulnerabilities that can be exploited.
  • Regularly update dependencies to their latest secure versions.
  • Utilize tools like npm audit or yarn audit to identify and fix vulnerabilities in dependencies.

Mitigation

  • Regularly update dependencies to their latest secure versions.
  • Use tools like npm audit or yarn audit to identify and fix vulnerabilities in dependencies.

Server-Side Rendering (SSR) vulnerabilities

  • SSR can introduce security risks if not implemented properly.
  • Ensure that user inputs are sanitized and validated on the server before rendering.
  • Use dangerouslySetInnerHTML cautiously and only when necessary.

Mitigation

  • Sanitize and validate user inputs on the server before rendering.
  • Use dangerouslySetInnerHTML cautiously and only when necessary.
  • Implement SSR frameworks with security considerations in mind.

Cross-Site Request Forgery (CSRF)

  • React apps can be vulnerable to CSRF attacks if they do not implement proper anti-CSRF measures.
  • Implement CSRF tokens and same-origin policies to prevent unauthorized requests.

Mitigation

  • Implement anti-CSRF tokens to validate requests.
  • Use same-origin policies and referer validation to prevent unauthorized requests.

Sensitive data exposure

  • Exposing sensitive information in the client-side code can lead to data breaches.
  • Avoid storing sensitive data like API keys, tokens, or credentials in the client-side code.
  • Utilize environment variables, server-side storage, or secure token providers for managing sensitive data.

Mitigation

  • Avoid storing sensitive data in client-side code.
  • Use environment variables or server-side storage for sensitive information.
  • Encrypt sensitive data before storing it.

Improper authentication and authorization

  • Improperly implemented authentication and authorization mechanisms can lead to unauthorized access.
  • Use established authentication libraries and frameworks, and implement proper authorization checks on both client and server sides.

Mitigation

  • Use established authentication libraries and frameworks.
  • Implement proper authorization checks on both client and server sides.
  • Apply the principle of least privilege when granting access to resources.

Code injection

  • Using eval() or Function() with user inputs can result in code injection vulnerabilities.
  • Avoid using these functions with untrusted data, and prefer safer alternatives like JSON.parse().

Mitigation

  • Avoid using functions like eval() or Function() with untrusted data.
  • Use safe alternatives like JSON.parse() for parsing JSON data.

Insecure data storage

  • Storing sensitive data in local storage or cookies without encryption can expose data to attackers.
  • Use secure storage mechanisms like sessionStorage or httpOnly cookies for sensitive data.

Mitigation

  • Avoid storing sensitive data in local storage or cookies.
  • Use secure storage mechanisms like sessionStorage or httpOnly cookies for sensitive data.

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.

<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>

Frequently Asked Questions

No items found.

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

Piotr Suwała
github
JavaScript Software Engineer

Full-stack software engineer with 8 years of professional experience. Passionate of React.js and React Native. Graduate of The Silesian University of Technology.

Bianka Pluszczewska
github
Tech Editor

Software development enthusiast with 8 years of professional experience in this industry.

Read next

No items found...

Get smarter in engineering and leadership in less than 60 seconds.

Join 300+ founders and engineering leaders, and get a weekly newsletter that takes our CEO 5-6 hours to prepare.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.