8 Performance Optimization Techniques You Should Know in React
Hey! I’m Çağlayan Yanıkoğlu and I work as a front-end developer at Jotform. I’m also a front-end instructor/consultant at Patika.dev and kodluyoruz.
Each Sunday, I’ll publish articles to give you some tech hacks, mainly about front-end development, using the #SundayTechMusings hashtag.
Today, I’ll share what I have learned from my work experience and research from blogs about performance optimization in React, which I use in my professional life and as well as my side projects. It will be a long post but I hope you’ll read through to the end :).

First, let’s define web performance and look at why it’s important.
Web performance refers to
- How quickly site content loads and renders in a web browser
- How well the site responds to user interaction
A good goal for web performance is for users to not even be aware of how a site is performing.
Web performance is important because it
- Creates a positive user experience
- Improves conversion rates: According to research by Akamai, a second delay in load time can cause a 7 percent reduction in conversions.

Site speed directly affects bounce rates, conversion, revenue, user satisfaction, and search engine ranking.
How React Works
Before trying to optimize performance, we first have to understand how React actually works.

We have two important keys in React: virtual DOM (document object model) and diffing.
Virtual DOM is actually a copy of the real DOM. When we create a rendered component, React creates a virtual DOM for its element tree in the component.
Then, whenever the state of the component changes, React recreates the virtual DOM tree and compares the result with the previous render. It then only updates the changed element in the actual DOM. This process is called diffing.
React uses the concept of a virtual DOM to minimize the performance cost of re-rendering a webpage because the actual DOM is expensive to manipulate.
This feature is great because it speeds up the UI render time. However, it can also slow down a complex app if it’s not managed well.
What we can deduce here is that a state change in a React component causes a re-render. Likewise, when the state passes down to a child component as a prop, it re-renders the child and so on.
Improving React performance
React applications guarantee a very fast user interface by default. However, as an application grows, developers may encounter some performance issues.
Here are some ways to optimize performance in React.
1. Keep states local, but why?
In React, when the parent component re-renders, all of its child components re-render. In most cases, this re-rendering shouldn’t cause performance issues.
However, if the unaffected component renders an expensive computation and we notice performance issues, then we should optimize our React application.

In the above code, when the input text changes, the App component will render. Although the ChildComponent’s state or props did not change, it will re-render.
For a small component like this, it may not be important to re-render, but think about more costly components.
So, how do we fix that?

In the code above, we created a new component — FormInput — and moved the state to the local component. Now if the FormInput state is changed, it won’t affect the ChildComponent.
So, we should keep states local if we can. For example; Modal states, search input states, etc.
2. Memoization
In this technique, we trade memory space for time. Memoization is an optimization strategy that caches a component-rendered operation. It saves the result in memory.
There are three techniques for memoization:
- React.memo (function component) / React.PureComponents (class component)
- useCallback hook
- useMemo hook
Using React.memo()
React.memo
is a higher-order component used to wrap a purely functional component to prevent re-rendering if the props received in that component never change.
React.memo()
works pretty well when we pass down primitive values, such as a number in our example. And, if you are familiar with referential equality, primitive values are always referentially equal and return true if values never change.
On the other hand, non-primitive values like object
, which include arrays and functions, always return false between re-renders because they point to different spaces in memory.
Using the useCallback
Hook
With the useCallback
Hook, the incrementCount
function only redefines when the count
dependency array changes
const incrementCount = React.useCallback(() => setCount(count + 1), [count]);
In the above example, if we don’t wrap the setCount
function with the useCallback
hook, then whenever input changes, ChildComponent will render again regardless of whether count or incrementCount
changes.
Because the function is object type, which is the referrer type, it will create another instance in every render. So when we use useCallback
, it will cache the instance of the function in every render so ChildComponent will not re-render.
Using the useMemo
Hook
When the prop we pass down to a child component is an array or object, we can use a useMemo
Hook to memoize the value between renders. As we’ve learned above, these values point to different spaces in memory and are entirely new values.
You can also use the useMemo
Hook to avoid re-computing the same expensive value in a component. It allows us to memoize these values and only re-compute them if the dependencies change.
Similar to useCallback
, the useMemo
Hook also expects a function and an array of dependencies.
useCallback vs useMemo

As you can see in the image above, useCallback caches the function in memory but useMemo caches the result.
Only memoize a component when necessary. If we use memoization techniques for all components or functions, we will increase the code complexity, which could affect performance.
So, when should we use React.memo, useCallback, or useMemo?
They can be used when
- Processing large amounts of data
- Working with interactive graphs and charts
- Implementing animations
- When the function has some internal state, e.g. when the function is debounced or throttled
3. React Fragments
Fragments allow you to group a list of child components together. Using fragments
- Reduces the number of extra tags
- Prevents extra DOM elements from rendering
Each component should have a single parent tag.
return (
<>
<h1>This is header</h1>
<p>Hello Medium</p>
</>
)
4. Code-splitting / Dynamic Import
The practice of bundling files allows you to combine multiple files together to reduce the number of individual HTTP requests a user makes to your server. However, as an application grows, the file sizes increase, thus increasing the size of the bundle file. At a certain point, this continuous file increase slows the initial page load, reducing the user’s satisfaction.
With code-splitting, React allows us to split a large bundle file into multiple chunks using dynamic import() followed by lazy loading these chunks on-demand using React.lazy. This strategy greatly improves the page performance of a complex React application.
import React, { useState, Suspense } from "react";
const Home = React.lazy(() => import("./components/Home"));
const About = React.lazy(() => import("./components/About"));
function App() {
const [page, setPage] = useState("home");
return (
<div>
{page === "home" && (
<Suspense fallback={<div>Loading...</div>}>
<Home />
</Suspense>
)}
{page === "about" && (
<Suspense fallback={<div>Loading...</div>}>
<About />
</Suspense>
)}
<div>
<button onClick={() => setPage("home")}>Home</button>
<button onClick={() => setPage("about")}>About</button>
</div>
</div>
);
}
export default App;
5. List Virtualization / Windowing

With windowing, you only render to the DOM the portion of the UI that the user sees. So, as a user scrolls, you replace the items that exit the viewport. This technique can greatly improve the rendering performance of a large list.
You can use npm package for windowing. The most popular npm packages are react-window and react-virtualized.
6. Lazy loading images
Similar to the concept of windowing mentioned before, lazy loading images prevents the creation of unnecessary DOM nodes, boosting the performance of our React application.
With lazy loading, you avoid rendering all images at once to improve the page load time.
Two popular npm packages for this are react-lazyload and react-lazy-load-image-component.

7. Throttling and Debouncing Events
Throttling means delaying the execution of a function if it exceeds a predetermined number of functions that can be performed in a given amount of time. An example of this is infinite scroll. If the throttle is being called, your function will fire periodically.
An example of debouncing is a search with an AJAX call. For example, say you want to add live search from an API on your website.
After each keystroke a user enters in a search query, you want to filter the results. But if you make this process in every keystroke, it could create problems, because the user will hit a key and make an API request, then after the response comes, React will render the screen while the user keeps writing.
So the best way to handle this is to delay making a request.
I also published a throttle example. You should check it :)
Use Infinitive Scroll Like a Pro
8. Web workers
JavaScript is a single-threaded application to handle synchronous executions.
While a web page gets rendered, it performs multiple tasks, including
- Handling UI interactions
- Handling response data
- Manipulating DOM elements
- Enabling animations
All these tasks are taken care of in a single thread.
Web workers are a way to reduce the execution load on the main thread.
Worker threads are background threads that can enable the user to execute multiple scripts and JavaScript executions without interrupting the main thread.
Whenever you have long-executing tasks that require a lot of CPU utilization, those logical blocks can be executed on the separate thread using workers.

How do you measure performance?
So, we just talked about how to make performance improvements. But how do we measure React performance to ensure those improvements are working? Here are a couple of tools to do just that.
1-) PageSpeed Insights
PageSpeed Insights is a tool created by Google to allow people to identify performance best practices on any website and get suggestions about optimizations that can be used to make that website faster.

As you can see in the image above, we can check web performance with the Lighthouse extension for Google Chrome. It will give us metrics for both desktop and mobile usage.
2-) Profiler
According to React, “The Profiler measures how often a React application renders and what the ‘cost’ of rendering is. Its purpose is to help identify parts of an application that are slow and may benefit from optimizations such as memoization.”
Profiling adds some additional overhead, so it is disabled in the production build. If you want to use it in production, you have to use Profiler API.
When deciding to use an optimization technique like memoization and particularly useCallback():
- Use the Profiler to measure the rate and cost of rendering an application.
- Quantify the increased performance you’re aiming for (e.g. 150 milliseconds vs 50 milliseconds render speed increase)
- Ask yourself: Is the increased performance, compared to increased complexity, worth using useCallback()?

Conclusion
To successfully optimize our React application, we must first find a performance problem in our application to rectify.

Because when you start a new project and try to implement all the tricks to improve performance, the code can become very complex, leading to a bad developer experience.
Long story short, if you have a problem with your web performance then you should look for ways to improve it.
Happy hacking! :)
If you find this content helpful and would like to show your support, you can buy me a beer🍺😊
Buy Beer🍺
Stackademic
Thank you for reading until the end. Before you go:
- Please consider clapping and following the writer! 👏
- Follow us on Twitter(X), LinkedIn, and YouTube.
- Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.