Optimizing User Experience in React Native
How do you make your app faster and improve the overall quality of it? Consider problems and practical solutions to increase the efficiency of the React Native app.
Join the DZone community and get the full member experience.
Join For FreeOne day, the company I work in decided that it needed a mobile app. Our front-end team volunteered to help with this interesting project. As we all know, React Native is the best way to start developing mobile apps if you are a front-end developer, especially if you are developing by using the React library. That's how we started working on our WMS app.
As far as the app was growing, we started thinking about its performance checking-up and ways to optimize it.
Our plan was:
- Define problem places
- Measure what is measurable (no guesswork)
- Analyze it
- Improve it if it's needed
- Keep it under control
By that time, our application contained two big parts — navigation and scrollable lists. We used different tools to check the performance of each one.
Basically, in most performance-checking tools, green means that it's actually good, yellow — there is a place for improvement, but the app could still work with it, and red means that your app screams for help along with its clients.
We defined some problems, and the first one was mostly related to rendering time of the Drawer component responsible for navigation:
App profiler by React DevTools
We also checked the performance of our app:
App performance by Flipper + RN Perf monitor
We saw that performance sagged to almost 0 in navigation. The problem has been known for a long time and it is described in the documentation. In a nutshell, when a new screen is pushed, React Navigation initially renders it off-screen and animates it into place afterward. So if there is a screen with a lot of components, it will easily take a few hundred milliseconds to render is pushed.
We had a lot of nesting navigators inside the app, so we faced this problem. Before fixes from the React Navigation library are released, there is one way to avoid this problem — don't use too many navigators and minimize nesting as much as possible. Also you can try to postpone heavy calculations with InteractionManager.
Second problem was related to our scrollable list:
Scrollable list profiler by React DevTools
Scrollable list
During the scrolling, we measured performance, and it showed us the next result:
Scrollable list performance by Flipper + RN Perf monitor
Not that bad, but for sure, there were a few places for optimizing. We began with the code and components. Basically, we had a FlatList
component with rendered data. The first thing to check was how we used memoization there. For example, we had to scan the barcode and fetch the specific data, and based on this data, it showed a relevant list. If we scanned the same barcode, the whole screen was re-rendered because it thought that there was some new data coming. To avoid this, we needed to analyze what components were re-rendered mostly and tried to memoize functions, variables, and props by using certain methods such as useCallback
, useMemo
, and React.memo
. But remember to use it locally only where it's actually needed because over-memoization might degrade your app performance even more. So you need to use useMemo
only on expensive computations, wrap component in React.memo
only if this component re-renders often with the same props and component is relatively big in size, and use useCallback
only if component accepts your function relies on the referential equality. Of course, it's not all the use cases but the common ones.
The next thing was to check how our main component of the scrollable list was implemented:
<FlatList
data={skuInfo}
renderItem={({ item: sku }) => <SkuCard {...sku} />}
keyExtractor={sku => sku.id}
/>
We got back to documentation and refreshed our memory. There was advice not to use an arrow function in renderItem
property. Also, a renderItem
component is supposed to be a memoized dumb component to avoid unnecessary re-renders.
// Fixes for SkuCard component
export default memo(SkuCard);
// Fixes for renderItem property of FlatList
const renderItem = useCallback(
({ item: sku }: { item: SKUInfo }) => <SkuCard {...sku} />,
[],
);
// How FlatList looks like after fixes
<FlatList
data={skuInfo}
renderItem={renderItem}
keyExtractor={sku => sku.id}
/>
We checked the profiler of the scrollable list after those changes:
Scrollable list profiler by React DevTools
It helped to optimise rendering part a lot. Also, we had a look at performance:
Scrollable list performance by Flipper + RN Perf monitor
Alright, much better now. For that moment, we had a small application, and the more it grows, the more we will need to monitor performance.
Also, remember that you have some nice tools from Shopify, such as FlashList
that you can try to implement in your app.
Conclusion
- Avoid using a large amount of navigators and minimize nesting as much as possible.
- Defer computation and heavy rendering with
InteractionManager
to avoid them affecting the performance of the UI - Try rendering placeholders for the heavy parts of your screens on the first render, and then after the animation finishes, replace them with actual content
- Profile different places of your app and see what is taking most of the time to define whether it is React Navigation, or is it your app components logic that can be optimized
- Use external libraries carefully. Compare performance before and after applying a specific library and decide what is the best for your app.
Opinions expressed by DZone contributors are their own.
Comments