DEV Community

Cover image for Building a performative React Native app
Ponikar
Ponikar

Posted on

Building a performative React Native app

Hi there,

It's 2026 and building an app with React Native could not become more easier. Thanks to the open-source React Native community and Expo ecosystem. In the era of AI, it enables anyone to build an app faster than ever.

However, building a performative React Native app is still challenging. Your MVP might work but not across all the mobile devices.

This blog covers the painful points on how to profile and build a performant React Native app that works smooth as much as possible, even on mid-range Android devices.

Profile

Before you decide to improve performance of your app, let's first understand what part of your app is really slow.

For starters, React Debugger helps a lot to understand things such as expensive re-rendering, initial load time, screen mount/unmount state.

Do not underestimate React Debugger for your React Native app, it can help you a lot to improve the performance.

React native flame graph

If you are more curious to profile your app beyond JavaScript, you can try profiling your app natively using Xcode or Android Studio.

You can see things like total memory being taken by your app in order to run it. Memory usage when you load a specific screen and perform certain operations.

Native profiling is optional, can be skipped if your app is not doing resource heavy operations like rendering videos or 3D graphics.

You can use IDEs like Android Studio or Xcode to profile your app natively. By doing so, you will understand the overall memory usage, native memory leaks, and CPU usage. This will give you a clear picture of how your app will work.

screenshot of Xcode

screenshot of android studio

At this point, you will have some sort of sense about which part of your app is slow. Now let's talk about what and how to optimise it.

Rendering large list

Rendering list can be quite challenging. For starters, if you have simple items to render, you can use FlatList or FlashList by Shopify. Both are straightforward solutions to render moderate lists.

Problem arises when the item takes time to render. To understand this in a much simpler way, consider each item in the list takes ~400ms. FlatList virtualization concept basically adds/removes items as user scrolls and it is pretty obvious to spot blanks in between the list because the CPU is most of the time busy performing virtualization.

In order to deal with this problem, there are solutions like FlashList which can recycle the item and make sure to reuse it. Another one is LegendList which follows the concept of reactivity and makes sure to not re-render item again if nothing changes.

LegendList is a robust solution for rendering large sets of items efficiently, accurately mitigating blank spaces between list items through precise positioning and optional recycling.

Legend-State pairs naturally with LegendList for scenarios requiring fine-grained reactivity, such as real-time data updates in large, dynamic lists where standard React state might trigger unnecessary re-renders.

Initial boot time

Gif of Huddle01 app

Boot time is the time which requires your app to load and run on the device. This time includes loading and executing JS bundle in the RAM.

As you have guessed it, if your app has a bloated JS bundle, it will take some split seconds to load it and users may feel frustrated waiting for the app to launch.

screenshot of JS bundler

You can use tools like webpack analyzer to understand which part of your bundle is large enough. You can look for third party dependency which is contributing most to the size and replace it with a lighter one.

Unlike web, React Native out of the box doesn't support code splitting entirely. It's a concept where your JS bundle gets divided into multiple chunks. This way client loads only required JS.

You can use Repack bundler which supports code splitting. It will require additional configuration to make it work.

As a good thumb rule, avoid bundling media assets like images/SVGs along with your business logic. Use remote servers to host and cache it using libraries like expo-image which loads and caches the assets once.

Again, profiling can help you to understand which part of your app is slow at rendering. It can be a particular screen or component of the app which requires more resources.

Illusion of lazy load

Earlier when I was building Huddle01 meet app, we noticed a lag in the Android app. Initially the component was taking time to load. We soon realised we are rendering too many components initially.

Our app has multiple bottom sheets in the meeting room. Initially we decided to render all the sheets at once. Android unlike iOS has limited resources, especially low-mid range Android devices.

Instead of rendering all the bottom sheets at once, we just toggled the sheet, in other words rendering only one sheet at a time. Our Android app became smooth just by applying conditional rendering.

As a rule of thumb, avoid rendering parts of your app which are not visible initially to users. Remember Android devices are resource constrained and cannot optimize it like iOS.

I call this technique an illusion of lazy loading. Use it with caution, you can selectively decide which component to conditionally render.

Optimise this technique with Intersection Observer. This way you completely avoid rendering components which are not visible at the time.

Synthetic sugar trade-offs

Avoid fancy JS libraries which offer nothing but just a fancy way of writing JavaScript. Remember your JS is 80% of your native app so every library you install is liable to performance issues.

Before you install any library, try to use a custom solution if possible. I would even recommend avoiding CSS libraries if you are targeting low-end Android devices.

Although there are lightweight libraries like react-native-unistyle which leverage compile time and avoid runtime overhead.

It's better to measure first before you decide to use any library.

Runtime overhead is the worst for your app.

Do not optimize for an API if you are targeting an audience which prefers using phones around $100.

Imperative way to handle UI changes

You might have heard about a method called setNativeProps, a way to update your UI without triggering render of the React tree. It is very important to use this very carefully because it can lead React state out of sync with the current UI. If re-rendering is becoming very costly then you may consider using this method.

React state triggers re-rendering components and sometimes we may end up rendering whole app. For example dark/light mode. It's pretty obvious since we want all the elements of the app to adapt the mode as soon as possible. If you use React state to manage this, you will see the lag when testing on low-end devices.

Animations

Running animations couldn't be more easier thanks to react-native-reanimated. It leverages UI threads to offload animations but be very careful when working with low-end Android devices.

If your priority is low-end devices, they don't have the luxury of multiple threads and it can backfire. You may see your animations being sluggish, laggy, or not working at all. Yes, this can happen with react-native-reanimated library. Although this may differ from device to device. It's always better to know what your target audience is.

If you are targeting low-end devices, it's better to stick to vanilla React Native animations to avoid randomness.

Leveraging resources like GPU

GPU Image from margelo.com

One of the hacks I found while working with multiple applications is leveraging resources like GPU when possible. While working with React Native we don't have support out of the box, but libraries like react-native-skia are so powerful that they let you use GPU to render graphics from JavaScript.

This feels like magic when your JS code can do realtime image manipulation and apply shaders that run completely on GPU.

There's one more library I would like to point out by Margelo, react-native-filament. This library renders 3D graphics completely on native GPUs. So now it's quite possible to create a Pokemon GO like app in React Native.

It's always better to use powerful libraries like these when possible to avoid overhead on JS threads and leverage device resources.

C++ magic

In recent years, React Native has improved significantly with the new architecture updates that completely get rid of the bridge, enabling access to native APIs from JS runtime using JSI (JavaScript Interface), which is written in C++.

One of the most skilled and talented C++ engineers and core contributor of react native I know, Marc and his team, have published several powerful libraries which leverage JSI modules that are completely written in C++.

For example, react-native-quick-crypto has a C++ implementation which does all the heavy lifting on the C++ side, keeping your JS thread completely non-blocking. C++ as we know is the most powerful and performant language to do all the performance intensive and low-level tasks.

Margelo, founded by Marc, has also released Nitro Modules, a new way of writing native modules which leverage highly optimized JSI and are end-to-end type-safe. You can check the benchmark here.

The reason I am emphasizing more on C++ is because thanks to AI now it's possible to write native modules based on the requirement, and Nitro Modules can help write faster and more robust native modules.

The line between native and JS is getting blurred as we move forward. C++ is bridging that gap and it couldn't be more easier to write your own native implementation which pushes your React Native limits.

Conclusion

Last 5 years were the golden period of React Native. With help of a supportive community, we are getting a truly "write once, run anywhere" codebase. But as we become more creative, we have to push the limit of existing technology. React Native may be limited to some domains but it will improve a lot, or there might be a successor of React Native which truly overcomes all the challenges we are facing now. We may never know but it's so fascinating to see how cross-platform technologies like React Native and Flutter have evolved.

Thanks for reading this blog. Let me know if you have any questions in the comment section.

Happy New Year.

Top comments (0)