Simple and effective optimization steps for better React app performance

Mayank Gupta
Jul 24 · 25 min read

This article is focused on optimizing the performance of a React application.

React offers a lot of optimization for designing high-performance applications which can be achieved by following some of these best practices.

Parent and child components are often re-rendered in the following scenario:

  1. When setState is called in the same component or parent component.
  2. Change in the value of “props” received from a parent.
  3. Calling forceUpdate in the component.

Here are 21 techniques we can use to improve the performance of our React applications.

1. Using Pure Components

A React component can be considered pure if it renders the same output for the same state and props.

For class components like this, React provides the PureComponent base class. Class components that extend the React.PureComponent class are treated as pure components.

It is the same as a normal component, except that PureComponents take care of shouldComponentUpdate — it does the shallow comparison on the state and props data.

If the previous state and props data is the same as the next props or state, the component is not re-rendered.

What is shallow rendering?

When comparing previous props and state to the next, a shallow comparison will check that primitives have the same value (example: 1 equals 1 or true equals true) and that the references are the same between more complex JavaScript values like objects and arrays.

Comparing primitive and object references is a cheaper operation than updating the component view.

Therefore, looking for the change in state and props’ values will be faster, instead of making unnecessary updates.

In the above example, the state is propagated to the child components RegularChildComponent and PureChildComponent. PureChildComponent is a pure component.

After an interval of one second, the setState is called, which will re-trigger the view rendering for the component. As the initial props and new props value is the same, the component (PureChildComponent) will not be re-rendered.

Shallow comparison of the state shows that there’s no change in the data for either props or state, therefore the component need not be rendered, making the component more effective.


2. Use React.memo for Component Memorization

React.memo is a higher-order component.

It is similar to a PureComponent, but PureComponent belongs to the class implementation for Component, whereas “memo” is used for the creation of functional components.

Similar to the pure components, if the input props are the same, the component rendering will be skipped making the component faster and more efficient.

It memorizes the output from the last execution for a certain input prop and boosts the application performance. Even in these components, the comparison is shallow.

You can also pass custom comparison logic for this component.

The custom logic can enable a user to look for the deep comparison of the object. If the comparison function returns false, the component will be re-rendered, otherwise, there is no re-rendering of the component.

The above component will do shallow comparison for the previous and the next props’ value.

In cases where we have object references passed as props to the memo component, some custom login needs to be in place for the comparison. In such cases, we can pass the comparison function as the second parameter to the React.memo function.

Let's assume that the props value (user) is an object reference, containing name, age, and designation for a particular user.

In this case, a deep comparison needs to be made. We can create a custom function that looks for the value of name, age, and designation for the previous and next props value and returns false if they are not same.

This way, our component will not re-render, even if we have reference data as input to the memo component.

The code above provides custom logic for comparison.


3. Using the shouldComponentUpdate Life Cycle Event

This is one of the life cycle events that is triggered before the component is re-rendered.

We can effectively utilize this event to decide when the component needs to be re-rendered. This function returns a Boolean value if the component props changes or setState is called.

In both cases, the component tends to re-render. We can put a custom logic in this life cycle event to decide if the component’s render function will be called or not.

This function takes nextState and nextProps as input and can compare it with current props and state to decide if there is a need for re-rendering.

Let’s understand it with the help of a scenario:

I want to display the employee details on the web page. Each employee contains several properties, such as name, age, designation, salary, current manager, previous manager, bonus, etc.

Out of all these details I want to render only the name and age for the selected employee on my web page. At some moment in time, designation for the employee updates.

As the employee designation is not part of the view, ideally, the view need not be updated. We can add custom logic in the component to see if we require the component to update the view or not.

Let’s see the scenario, with the help of the program:

Here, even when the designation changes in the component, there will be no impact on the view of the application.

As the setState is called, the component tends to re-render because the designation change does not alter/impact the view of the component, so re-rendering the component on a change of the designation will be an overhead.

To avoid this overhead, we can have a custom logic to check if either name or age is getting updated, because the view is only impacted by Name or Age.

shouldComponentUpdate takes the input parameter as the new value of state and props.

We can compare the current and new value of name and age. If any of them change, we can trigger the re-rendering.

Passing true from shouldComponentUpdate notifies that the component can be re-rendered and vice versa. So, if shouldComponentUpdate is used correctly, we can optimize the performance of the application component.

Comparing the initial states and props, we can make a decision about whether the component needs to be re-rendered or not. And this would enhance the performance of the application by making fewer re-rendering cycles.


4. Using Lazy Loading of Components

Bundling is the process of importing and merging multiple files into a single file so that the application doesn’t have to import a lot of external files.

All the main components and the external dependencies are merged into a single file and sent over the network to have the web application up and running.

This saves a lot of network calls, but also leads to a problem where this single file becomes a large file and consumes lots of network bandwidth.

The application keeps waiting for this large file to be loaded and executed, so delays in transmission of this file over the network lead to higher rendering time for the application.

To resolve this problem, we can incorporate the concept of code splitting.

The concept of code splitting is supported by bundlers like webpack which can create multiple bundles for the application and which can be dynamically loaded at runtime.

Loading on runtime reduces the size of the initial bundle that is created.

We can plan to break down bundles in a way so the components that are not loaded initially in the application can be deferred to be loaded later, when they are required.

This will reduce the size of the main bundle and reduce the load time of the application. We use Suspense and lazy for this.

In the code above, we have a conditional statement that looks for the props value and, according to the condition specified, loads either of the two components in the main component.

Loading both these components initially in the main bundle will increase the overall size of the bundle. At any moment of time, we require one of the two components to be rendered.

So, loading all the components that may or may not be added in the view will be a performance degrade.

We can lazily load components when required and these components are part of a separate chunk, loaded at runtime, which enhances the overall performance of the application.

We can understand this with the help of another example.

Let's assume that there are two different components that are rendered on the basis of whether the user is logged in or not.

One of the two components is rendered: WelcomeComponent or GuestComponents, depending on whether the user is logged in or not.

Rather than loading both the components in the initial bundle file, we can delay the loading of the component on the basis of the condition later.

In the code above, we could have preloaded both the components above using the import keyword on the top of the component, but, instead of preloading the components WelcomeCompoment and GuestComponents, we make a conditional check.

If the user name exists or not, and on the basis of the condition specified, we decide which component is required to be loaded as a separate bundle.

So that the initial bundle will not contain both of these components, a new bundle will be loaded on the fly according to the condition specified.

The benefits with the approach

  1. The main bundle will reduce in size because WelcomeCompoment and GuestComponents are not loaded in the initial bundle, consuming less network time for the initial bundle loading.
  2. A separate request to load the required component is made on the fly, according to the condition specified. The separately loaded bundle is a small bundle file and can be loaded without much delay.

We can look for the application to decide which components can be loaded later, reducing the initial load time of the application.


5. Use React Fragments to Avoid Extra Tags

Using fragments reduces the number of extra tags which are included, only to fulfill the requirement of having a common parent in the React component.

In the case where a user is creating a new component, each component should have a single parent tag. Two tags cannot reside at the parent level, so we need to have a common tag on the top.

To cater to this requirement, we often add an extra tag on the top of the component.

See the example below:

In the component specified above, we need to have an extra tag to have a common parent for the component to render.

This extra div does not have any other purpose besides acting as a parent tag for the component. It is added only because the component cannot have two parent tags.

Having multiple tags at the top level leads to the following error:

Therefore, just as a formality, we need to have an extra tag which encapsulates the tags that lie at the same level.

To resolve the problem, we can enclose the elements inside a fragment.

The fragment does not introduce any extra tag to the component but it still provides the parent to the two adjacent tags, so that the condition of having a single parent at the top level of the component is satisfied.

In the above code, there is no extra tag to enclose the tags, hence saving efforts for the renderer to render the extra elements in the page.

Refer to the following URL for more insights:


6. Do Not Use Inline Function Definition

If we are using the inline functions, every time the “render” function is called, a new instance of the function is created.

When React does the virtual DOM diffing, it finds a new function instance each time, so during the rendering phase, it binds the new function and leaves the old instance for garbage collection.

So, directly binding the inline function leads to extra work on the garbage collector and new function binding to the DOM.

Below is an example of the inline function in the component:

The function above creates the inline function. Every time the render function is called, the new instance of a function is created and the render function binds the new instance of the function to the button.

Also, the last function instance is available for the garbage collection, thus increasing a lot of effort for the React application.

Instead of using the inline function, create a function inside the component instead and bind the event to that function itself. Doing so will not create a separate function instance every time render is called.

For reference, see the component below.


7. Avoid Async Requests in componentWillMount()

componentWillMount will be called right before the component is rendered.

This function is not used a lot. It can be used to configure initial configuration for the component but this can be done with the constructor method itself.

The method cannot access the DOM elements as the component is still not mounted.

Although, some developers think that this is the function where the async data API calls can be made. However, there is no benefit to this.

As the API calls are asynchronous, the component does not wait for the API to return data before calling the render function. So, the component is rendered without any data in the initial rendering.

In the above code, we are making an asynchronous call to fetch the data. As the data call is asynchronous, it will take a while to be fetched.

While the data is retrieved, React triggers the render function for the component. Therefore, the first render that is called will still not contain the data required for it.

So, initially, the component is rendered with empty data and later the data is retrieved, setState is called, and the component is re-rendered. There is no big benefit to making AJAX calls in the componentWillMount phase.

We should avoid making Async requests in this function. Rather, such functions and calls can be delayed to the componentDidMount life cycle event.

Note: componentWillMount is deprecated in React 16.3. If you are working with the latest versions of React, avoid using this life cycle event.


8. Bind Function Early in Constructor

When we create functions in React, we need to bind the function to the current context, using the bind keyword.

The binding can be done either in the constructor or the place where we are binding this function to the DOM element.

There doesn’t appear to be a big difference between the two, but the performance impacts are different.

See the code below for more details:

In the above code, we bind the functionality to the button during binding in the render function.

The problem with the code above is that each time the render function is called, a new function bound to the current context is created and used.

It is more efficient to use the already-existing function each time while rendering, rather than creating a new function on each render.

Let's see how we can optimize the code for that:

The code above optimizes the problem of recreating the function every time the render function is called.

Instead of binding the function every time on render, it is better practice to override the handleButtonClick function with a function bound to the current context during the constructor call itself.

This will reduce the overhead of binding the function to the current context and recreating the function every time on render, hence enhancing the performance for the application.


9. Arrow Functions vs Binding in Constructors

Using arrow functions is standard practice when you’re working with the classes. If we use the arrow functions, the context of the execution is preserved.

We don’t need to bind the function to the context while invoking it.

Arrow functions seem to be a great advantage but with the benefit comes a downside as well.

When we add an arrow function, the function is added as the object instance and not the prototype property of the class. Which means that, if we reuse the component multiple times, there will be multiple instances for these functions in each object created out of the component.

Each component will have a separate instance for these functions and the re-usability is reduced. Also, as it’s the object property and not the prototype property, the functions are not available in the inheritance chain.

So, although the arrow function seems to be useful and easy to implement, it does have its downsides. The best way to implement the functions is to have function binding in the constructor, as specified in the above point.


10. Avoid Using Inline Style Attribute

With inline styles, the browser spends a lot more time scripting and rendering.

A lot of time is spent on scripting because it has to map all the style rules passed to the actual CSS properties, which increases the rendering time for the component.

In the component created above, we have inline styles attached to the component. The inline style added is a JavaScript object instead of a style tag.

The style backgroundColor needs to be converted into an equivalent CSS style property and then the style will be applied. Doing so involves scripting and performing JavaScript execution.

The better way is to import the CSS file into the component.


11. Optimize Conditional Rendering in React

Mounting and unmounting React components are costly operations. To ensure better performance of the application, we need to ensure that the number of mounting and unmounting operations is reduced.

There are multiple situations where we have conditional rendering of the components, on the condition that we may or may not render specific elements.

See the scenario below for more details.

In the code above, we have a conditional statement where the component is rendered according to the condition specified. If the state contains the name value Mayank, the AdminHeaderComponent is not rendered.

The conditional operator and if else condition seem to be fine but the following code has a performance flaw.

Let's evaluate the code above.

Each time the render function is called and the value toggles between Mayank and another value, a different if else statement is executed.

The diffing algorithm will run a check comparing the element type at each position. During the diffing algorithm, it sees that the AdminHeaderComponent is not available and the first component that needs to be rendered is HeaderComponent.

React will observe the positions of the elements. It sees that the components at position 1 and position 2 have changed and will unmount the components.

The components HeaderComponent and ContentComponent will be unmounted and remounted on position 1 and position 2. This is ideally not required, as these components are not changing, but still, we have to unmount and remount these components.

This is a costly operation. The following code can be optimized like this:

In the above code, when the name is not Mayank, React puts null at position 1.

When the DOM diffing occurs, the element at position 1 changes from AdminHeaderComponent to null, but the components at position 2 and position 3 remain the same.

As the elements are the same, the components are not unmounted, hence reducing Unmounting and Mounting of components in the application.

For more in-depth documentation, refer to the following:


12. Don’t Derive Data in the Render Method

The Render method is the most familiar life cycle event for React developers.

Unlike any other life cycle event, we have the core principle of having the render() function as a pure function.

What does pure function mean for the render method?

Keeping the function pure means that we should ensure that the setState, query to the native DOM element, and anything that can modify the state of the application is not invoked or called.

The function should never update the state of the application.

The problem with updating the state of the component is that, when the state updates, it triggers another Render cycle, which, internally, can trigger another Render cycle and this can go on recursively.

In the code above, every time the render function is called, it updates the state. As soon as the state updates, the component is re-rendered. Hence, updating the state can result in a recursive invocation of the render function.

The render function should be kept Pure to ensure that the component behaves and renders in a consistent manner.


13. Create Error Boundaries for the Components

You can easily run into a situation where the component rendering results in an error.

In such cases, the component error should not break the entire application. Creating error boundaries ensures that the application does not break in case of an error in the specific component.

Error boundaries are a React component that catches JavaScript errors anywhere in the child component. We can contain the error, log the error messages, and have a fallback mechanism for the UI component failure.

Error boundaries are based on the concept of higher-order components.

For more details on higher-order components, refer to the following:

Error boundaries involve a higher-order component, containing these methods: static getDerivedStateFromError() and componentDidCatch().

The static function is used to specify the fallback mechanism and derive the new state for the component from the error received.

The componentDidCatch function is used to log error information to the application.

Refer to the code below for error boundaries:

The code above throws the error when the name is updated to Anshul.

The component ShowData are the embeds inside the ErrorBoundaries component.

So, if the error is thrown from the ShowData function, it is captured in the parent component and we deploy the fallback UI using the static getDerivedStateFromError function and log data in the componentDidCatch life cycle event.


14. Immutable Data Structures for Components

React is centered around and focused on functional programming. The state and props data in the React component should be immutable if we want a component to work consistently.

Mutation of objects can result in inconsistent output.

Let's see the code below to understand:

We can see that inside the shouldComponentUpdate function we specified that, if the initial value of userInfo is different from the new value of userInfo, the component should be re-rendered. If not, it should not re-render the component.

For more details refer to the following link:


15. Using a Unique Key for Iteration

When we need to render a list of items, we should add a key for the items.

Keys help in identifying the items that have been changed, added, or deleted. Keys give a stable identity to the element. A key should be kept unique for each element on the list.

If the developer does not provide the key to the element, it takes an index as default key. In the code below, we don’t add any key by default, so index will be used as the default key for the list.

Using index as a key will resolve the problem of maintaining the unique identity for the component, as the index will uniquely identify the component that is rendered.

We can use index as key in the following scenario:

  1. The list items are static, items do not change with time.
  2. The Items have no unique IDs.
  3. The List is never reordered or filtered.
  4. Items are not added or removed from the top or the middle.

Adding items to the list

Using index as a key introduces the chance of errors and reduces the performance of the application.

Whenever a new element is added to the list, by default, React traverses on the newly created list and the old list at the same time, and makes a mutation wherever required.

When a new element is added on the top of the list, containing index as key, the indexes of all the components that already existed update.

The element which initially had key as 1 now has a key value of 2 as the index of the element has updated. This results in React interpreting the fact that all the components have changed, and updates are done to all the components in the list, which leads to lower performance.

The code above allows the user to add a new item on the top of the list. Inserting an element on the top has the worst effect.

Once the item has been added to the list, React compares the original list and the new list. It sees that the element on the top has changed, tries to evaluate the element with the key-value of 1 in the old list with the element that has a key value of 1 in the new list, observes that there are changes, and ends up re-rendering that element.

A similar comparison is done for the other component as well. As the index of all the elements has been updated, all the items in list are updated, leading to performance degrades.

So, we should always ensure that the value we use as a key always represents a value that can uniquely identify the element from the list.

So, to summarize:

  • key is not just about performance, it’s more about identity (which in turn leads to better performance). Randomly assigned and changing values are not identity.
  • We can’t realistically provide keys without knowing how your data is modeled. I would suggest using some sort of hashing function if you don’t have IDs.
  • We already have internal keys when we use arrays but they are the index in the array. When you insert a new element, those keys are wrong.

For more details, refer to the links below:


16. Throttling and Debouncing Events

Throttling and debouncing can be used to control the number of event handlers that are invoked in a specified amount of time.

Event handlers are the functions that are invoked in response to the different events, like mouse click and page scroll. Events have a different rate of triggering the event handler.

Throttling concept

Throttling means delaying the function execution.

The functions are not executed immediately, a few milliseconds are added before the triggering of the event occurs.

In the case of page scrolling, instead of triggering the scroll event too frequently, we delay the event for some time so that multiple events can be stacked together.

It ensures that the function will be called at least once in a specific time period. It means that it will prevent a function from running if it has run recently. It ensures that the function runs regularly in fixed intervals.

When we have infinite scroll and the data has to be fetched as the user approaches the bottom of the page, we can use throttling.

If throttling is not used, each page scroll towards the bottom of the page will trigger multiple events, and multiple calls to the network are triggered, leading to performance concerns.

Debouncing concepts

Debouncing refers to ignoring the event handler call until the calls have stopped for a certain amount of time.

Let's imagine that we have an event with a debounce time of one second. The event handler for that event will be triggered after one second, once the user stops triggering the event.

The classic example for this is when the user types data in the autocomplete search box.

As soon as the user stops typing, an AJAX query is made to get the data from the APIs. Making AJAX calls on each keypress will lead to multiple queries to the database.

So, we debounce the event until the user has stopped typing more data in a flow, triggering fewer network calls and better performance for the application.

We can use third-party libraries to implement and use the throttling and debouncing features. One such library is throttle-debounce. Refer to the link below to implement throttling using this library.


17. Using CDNs for External Resources

There are a number of content delivery networks offered by Google, Amazon, Microsoft, and many other companies.

These CDNs are the external resources that can be used in your application. We can even create private CDNs and host our files and resources.

It is beneficial to use the CDNs for the following reasons:

1. Different domains

Browsers restrict the concurrent connection to a single domain to a specific number, which can vary from browser to browser.

Let's assume that the number of concurrent connections allowed is 10. Where there are 11 resources to be retrieved from a single domain, only 10 can be retrieved at a single moment of time concurrently, making one of the requests wait for another to complete.

CDNs are hosted on different domain/servers. So, the resource files can be distributed among the different domains so that multiple requests can be catered for at a single moment of time.

2. Files might have been cached

Multiple websites use these CDNs, so there’s a good probability that the resource you are trying to access has already been cached in the browser.

Therefore, the application will access the already-cached version of the file, which reduces network calls and latency in script and file executions. This adds to the performance of the application.

3. High capacity infrastructure

These CDNs are hosted by huge companies, so the kind of infrastructure is available is huge. They have distributed data centers across the globe.

When a request to a CDN is made, they are catered for via nearest data center available, reducing latency.

These companies load balance the servers to ensure that the request goes to the nearest server and reduces the network latency, making the application perform better.

These are a few of the benefits provided by CDNs. Use CDN’s, or consider create private CDN’s, if security is a big concern.


18. CSS Animation Instead of JavaScript Animation

There are multiple ways to work with animation these days.

Before HTML5 and CSS3, animations used to be the domain owned by JavaScript, but with the introduction of HTML5 and CSS3, the responsibility of providing the animation started overlapping. The animation can even be handled by CSS3.

We can formulate some rules now, as the responsibilities overlap:

  • If something can be achieved by CSS over JavaScript, do it.
  • If something can be achieved by HTML over JavaScript, do it.

Below are some reasons to justify this:

  1. Broken CSS rules and styles do not lead to errors of broken web pages, which is not the case with JavaScript.
  2. CSS is quite cheap to interpret as it’s declarative. We can parallelize the creation of in-memory representation of style and we can defer the calculation of style properties until the elements are painted.
  3. The cost of loading a JavaScript library for animation is relatively high, consuming the network bandwidth as well as computation time.
  4. Even though JavaScript can provide a lot more optimization than CSS, even an optimized JavaScript code can lock the UI and lead to failure of the web browser.

There are other parameters for deciding which one would be more performant in your scenario.

For more details, you can refer to the following:


19. Enable gzip Compression on the Web Server

Compression is the simplest way to save network bandwidth and speed up the application.

We can enable the network resource to zip them to a smaller size. Gzip is a data compression algorithm capable of compressing and decompressing files quickly.

It can compress almost any type of file, such as images, text, JavaScript files, style files, etc. On the web page, gzip reduces the amount of data transferred to the client.

When a web server receives the request, it extracts the file data and looks for the Accept-Encoding header to determine how to encode the application.

If the server supports compression using gzip, the resources are compressed and sent over the network. The compressed copies of each resource, with added Content-Encoding headers, specify that the resource is encoded using gzip.

The browser then decompresses the content into its original uncompressed version before rendering it to the user.

Although the gzip compression comes at a cost, there is a CPU intensive task that is triggered on compression and decompression of the files. Even then, it’s recommended to use gzip compression for web pages.


20. Use Web Workers for CPU Extensive Tasks

JavaScript is a single-threaded application to handle all the synchronous executions.

While a web page gets rendered, it needs to perform multiple tasks:

Handling UI interactions, handling response data, manipulating DOM elements, enabling animations, etc. All these tasks are taken care of by a single thread.

Workers can be seen as an option to reduce the execution load on the main thread.

Worker threads are the background threads that can enable the user to execute multiple scripts and JavaScript execution without interrupting the main thread.

Whenever there are long executing tasks that require a lot of CPU utilization, those logical blocks can be executed on the separate thread using workers.

They execute in an isolated environment and can interact with the main thread using inter-process thread communication. Meanwhile, the main thread can take care of the rendering and DOM manipulation tasks.

Look at the following URL for more detailed examples:


21. Server-Side Rendering of the React Component

Server-side rendering is one of the ways to reduce the delay in initial page loading.

We can enable the web page to load the initial page from the server-side itself, rather than rendering on the client. It adds a significant boost in search engine optimization for the web page.

Server-side rendering is when the content of the first component displayed is sent from the server itself, rather than manipulated at the browser level. Further pages are loaded directly from the client.

So, it gives us the power of initial server-side content rendering and partial page load on the client-side for future requests.

Here are the other benefits:

  1. Performance: The initial page content with the data is loaded from the server itself, so we don’t need to add loaders and spinners, and wait for the initial component to load after the initial page load on the browser.
  2. SEO optimization: Crawlers look for the page content on the initial load of the application. When you are dealing with client-side rendering, the initial web page does not contain the required component. These components are later rendered on the web page once the React script and other related files are loaded.

One of the options for server-side rendering is to use third-party libraries like Next.js. Details can be found on the following website:

You can also find a sample project on server-side rendering below. To run the application, simply perform the following steps from the project repository:

  1. npm install
  2. npm start

The files that are in the “pages” folder of the application are the initial URLs that can be loaded using server-side rendering.

For further clarification, you can contact me.

22. Use React Hooks

React hooks are the new addition to the React 16.8 Version. It is based out of functional programming. React can now use Functional Programming to create State Components. For more details, refer to these documents:


Better Programming

Advice for programmers.

Mayank Gupta

Written by

9 Years of Experience with Front-end Technologies and MEAN Stack. Working on all Major UI Frameworks like React, Angular and Vue.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade