React is, arguably, the easiest UI library out there. But this easiness comes at a cost, which is performance. In this article, I will list some ways to improve the performance of your React app drastically.
Use keys!
This may be obvious, but it is imperative. If you render anything as a list (using .map
, or something like FlatList
), keys are used by React under the hood to optimize re-renders. If you miss keys, or, even worse, make them not unique, not only your app will perform slow, but it may lead to some frustrating bugs. So, this code:
data.map(item => <SomeComponent item={item} />)
Becomes:
data.map(item => <SomeComponent key={item.id} item={item} />)
You may be tempted to use index as a key:
data.map((item, index) => <SomeComponent key={index} item={item} />)
Let me stop you right there. This would only work if items in data
are never rearranged, deleted, or inserted. Even if this is the case now, think a few steps ahead: will you need to mutate data
a few sprints down the road? I thought so.
Use virtualized lists!
Closely related to the previous point, this applies when you need to render large amounts of listed data. There are a few libraries for both React and React Native projects that provide similar functionality. The way virtualized lists work is that only the few items that are currently visible in the viewport are rendered, and everything else is lazily (on-demand) rendered as you scroll.
In React, your choices are react-window
and react-virtualized
for slightly more complicated use-cases. For example, using react-window
will turn this code:
data.map(item => <SomeComponent key={item.id} item={item} />);
Into this:
const Row = ({index}) => <SomeComponent item={data[index]} />
// later, in render
return (
<List height={150} itemCount={data.length}>
{Row}
</List>
);
Now you can have as much items in data
, without worrying that they will take forever to render!
In React Native, the built-in FlatList
is used:
<FlatList
data={data}
renderItem={({item}) => <SomeComponent item={item} />}
/>
Use React profiler!
If after implementing the above 2 techniques you are still not satisfied with the performance of your React app, consider using the React profiler. It will tell you exactly what rerenders, when, how long does it take, and, most importantly, what caused the rerender in the first place.

The React profiler is built-in to the React devtools extension, so, most likely, you already have it installed. If not, head here. For React Native devs, there is no easy way to do this, unfortunately. There are 2 ways of profiling a React Native app:
- Open the dev menu on your phone/simulator (shake/Ctrl+M) and press on Enable Performance Overlay. It will provide a small window that shows current FPS of both the UI and the JS threads, as well as the number of skipped frames. Using it, you can roughly where the problem is.
- Use
systrace
for Android and XCode for IOS. These are low-level tools, that require some advanced setup. This is out of scope of this article, so read this to learn more.
Use useMemo!
Chances are, you are doing some data processing in your app. Chances are, you do not have to reprocess it on every rerender. useMemo
is designed specifically for this.
useMemo
takes in a function and a list of dependencies. Then, it will remember the value that the function returns, and will not call the function again unless one of the dependencies change. For example:
const importantData = useMemo(() => calculateImportantData(rawData, [rawData]);
To learn more about useMemo
, you can read my article explaining it in detail.
Use React Concurrent Mode?
Proceed with caution. React Concurrent Mode is a major rewrite of the React internals, that allow asynchronous rendering and multiple Virtual DOM trees. It gives a tremendous performance increase, but this feature is still experimental and not suited for production apps. Read more about how it works and how to enable it here.
Closing notes
Thank you for reading, I hope you learned something new about optimizing React code. Stay tuned for more content!