The Ultimate Guide To React Native Optimization: 2023 Edition
The Ultimate Guide To React Native Optimization: 2023 Edition
to React Native
Optimization
2023 Edition
The Ultimate Guide to React Native Optimization
Table of Contents
How this Guide is organized 3
Introduction toReact Nativeoptimization 6
Part one
Pay attention to UI re-renders 10
Use dedicated components for certain layouts 26
Think twice before you pick anexternal library 36
Always remember to use libraries dedicated to the mobile platform 42
Find the balance between native and JavaScript 48
Animate at 60FPS - no matter what 56
Replace Lottiewith Rive 67
Optimize your app’s JavaScript bundle 76
Part Two
Always run the latest React Native version to access the new features 83
How to debug faster and better with Flipper 94
Avoid unusednative dependencies 100
Optimize yourapplication startup time with Hermes 106
Optimize yourAndroid application’s size with these Gradle settings 114
Experiment withthe New Architectureof React Native 121
Part Three
Run tests for key pieces of your app 133
Have a workingContinuousIntegration (CI)in place 144
Don’t be afraid to ship fast with ContinuousDeployment 151
Ship OTA(Over-The-Air) whenin an emergency 162
Make your app consistently fast 171
Know how to profile iOS 185
Know how to profile Android 193
1. Always run the latest React Native version to access the latest
features
2. How to debug faster and better with Flipper
3. Avoid unused native dependencies
4. Optimize your Android application startup time with Hermes
5. Optimize your Android application’s size with Gradle settings
6. Experiment with the New Architecture of React Native
4
Organizational part The Ultimate Guide to React Native Optimization
Issue
This part describes the main problem with React Native performance.
Solution
This part outlines how that problem may affect your business and what
the best practices are to solve it.
Benefits
This part focuses on the business benefits of our proposed solution.
5
The Ultimate Guide to React Native Optimization
Introduction to
React Native
Optimization
Introduction The Ultimate Guide to React Native Optimization
However, that doesn’t mean all the applications developed with React
Native are equally fast and offer the same level of user experience.
7
Introduction The Ultimate Guide to React Native Optimization
Your job is to define the UI components and forget about the rest. How-
ever, that doesn’t mean that you should take the performance of your
application for granted. In order to create fast and responsive applica-
tions, you have to think the React Native way. You have to understand
how the framework interacts with the underlying platform APIs.
8
The Ultimate Guide to React Native Optimization
PA RT 1
Improve performance by
understanding the details
of React Native implementation.
In this section, we will dive deeper into the most popular performance
bottlenecks and the React Native implementation details that contrib-
ute to them. This will not only be a smooth introduction to some of
the advanced React Native concepts, but it will also let you improve
the stability and performance of your application by performing small
tweaks and changes.
The following part is focused on the first point from the checklist of
performance optimization tactics: UI re-renders. It’s a very important
part of the React Native optimization process because it allows for the
reduction of the device’s battery usage which translates into a better
user experience for your app.
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 1
Pay attention
to UI re-renders
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
In other words, when and how to repaint things on screen is purely React
Native’s responsibility. React looks out for the changes you have done
to your components, compares them, and, by design, only performs
the required and smallest number of actual updates.
Diff
Model
Patch
Diff
11
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
As a result, you may observe your UI flickering (when the updates are
performed) or frames dropping (while there’s an animation happening
and an update is coming along).
12
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
useEffect(() => {
setTimeout(() => {
setValue('update 1');
}, 3000);
setTimeout(() => {
setValue('update 2');
}, 5000);
}, []);
return (
<View style={backgroundStyle}>
<ColoredView />
</View>
);
};
13
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
In the Flipper app, make sure the “Record why each component ren-
dered while profiling” option is enabled in the settings icon and hit the
round blue button to start profiling. After around 5 seconds, hit the round
red button to stop profiling. Your profile will look something like this:
Taking a look at the performance flame graph for the first time may be
slightly intimidating. To understand React DevTools more in-depth, this
video from Ben Awad is good at explaining it. Don’t forget to watch this
talk by Alex at React Native EU, which explains how we can use flame
graph to identify and fix the issues. Also, visit the official react website
for detailed information on React Profiler.
14
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
return (
<TextInput
accessibilityLabel="Text input field"
style={styles.textInput}
onChangeText={onChangeText}
value={value}
/>
);
};
15
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
The above code sample will work in most cases. However, on slow
devices, and in situations where the user is typing really fast, it may
cause a problem with the view updates.
As soon as the user starts inputting a new character into the native
input, an update is sent to React Native via the onChangeText prop (op-
eration 1 on the above diagram). React processes that information and
updates its state accordingly by calling setState. Next, a controlled
component synchronizes its JavaScript value with the native compo-
nent value (operation 2 on the above diagram).
16
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
Diagram that shows what happens while typing TEST too fast
When the updates via onChangeText arrive before React Native syn-
chronized each of them back, the interface will start flickering. The
first update (operation 1 and operation 2) performs without issues as
the user starts typing T.
Now, the user was typing fast enough to actually enter another char-
acter when the value of the text input was set to TE for a second. As a
result, another update arrived (operation 6), with the value of TET. This
wasn’t intentional - the user wasn’t expecting the value of its input to
change from TES to TE.
17
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
The root cause of this situation lies in the order of operations. If opera-
tion 5 was executed before operation 4, things would have run smoothly.
Also, if the user didn’t type T when the value was TE instead of TES, the
interface would flicker but the input value would remain correct.
return (
<View style={styles.container}>
<TextInput
accessibilityLabel="Text input field"
placeholder="Type here to translate!"
onChangeText={onChangeText}
defaultValue={value}
style={styles.textInput}
/>
<Text style={styles.label}>
{value
.split(' ')
🍕
.map((word) => word && ' ')
.join(' ')}
</Text>
</View>
);
};
18
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
},
label: {
padding: 10,
fontSize: 42,
},
});
Global state
Another common reason for performance issues is how components
are dependent on the application's global state. The worst case scenario
is when the state change of a single control like TextInput or CheckBox
propagates the render of the whole application. The reason for this is
a bad global state management design.
First, your state management library should take care of updating com-
ponents only when a defined subset of data had changed - this is the
default behavior of the redux connect function.
19
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
20
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
Atomic State
The pattern of a single store - a top-down mental model - as initially
promoted by Redux is moving towards a more atomic design.
If you don’t pay attention to the Redux store, it tends to absorb all the
states, leading to a monolithic structure that’s quite hard to reason with.
The top-down build places most of the states at the top of the compo-
nent; because of this, a state update from a parent component could
produce re-renders to its children. Ideally, if possible, the state should
be local to the component so it can be reused - this means building
from the bottom-up.
Let’s say we have a TODO list with some filters: “show all”, “just the
active ones”, or “just the completed ones”. We identify the top compo-
nent, the TodoList, but we don’t start here. We first identify the children
and start building those smaller components, so that later we can mix
21
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
them and build a complex element. We need to make some data visible
in one component which will be managed by another one. To avoid a
parent state and passing down the data and actions (top-down), we are
going to use a state manager. This state manager will be in charge of
storing the data, making it accessible, and providing actions/modifiers,
because we are moving to a bottom-up approach. We are going to use
some libraries that will help us.
Zustand
Zustand is often used as a top-down pattern but given the simplicity and
unopinionated library, we can use it to build a component bottom-up.
Store
We create the obj where the filter will be and the modifiers will be
exposed:
22
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
Todo item
Here because we are looking at a filter, TodoItemList will only re-render
when it changes.
return (
<View>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
</View>
);
};
Jotai
Jotai was built with this bottom-up approach in mind, so its syntax is
minimal and simple. Using the concept of atom as a “store” then you
use different hooks to make the atom readonly or mutable.
Store
We use useAtomValue to read the filter value and useSetAtom to set a
new value. This is especially useful when performance is a concern.
FilterMenuItem
23
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
TodoItem
if (!shouldBeShown(filter, item.done)) {
return null;
}
return (
<View>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
</View>
);
};
24
Pay attention to UI re-renders The Ultimate Guide to React Native Optimization
With all these steps in mind, your application should perform fewer
operations and need smaller resources to complete its job. As a result,
this should lead to lower battery usage and more satisfaction from
interacting with the interface.
25
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 2
Use dedicated
components for
certain layouts
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
27
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
If you’re not using specialized components, you are opting out of per-
formance improvements and risking a degraded user experience when
your application enters production. It is worth noting that certain issues
remain unnoticed while the application is developed, as mocked data
is usually small and doesn’t reflect the size of a production database.
Specialized components are more comprehensive and have a broader
API to cover than the vast majority of mobile scenarios.
const objects = [
['avocado', ' '], 🥑
['apple', ' '], 🍏
['orange', ' '], 🍊
['cactus', ' '], 🌵
['eggplant', ' '], 🍆
['strawberry', ' '], 🍓
['coconut', ' '], 🥥
];
return {
name: item[0],
icon: item[1],
28
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
return (
<View style={styles.container}>
<Button title="add item" onPress={addItem} />
<ScrollView>
{items.map(({ name, icon, id }) => (
<View style={styles.itemContainer} key={id}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.icon}>{icon}</Text>
</View>
))}
</ScrollView>
</View>
);
};
29
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
const objects = [
'avocado ', 🥑
'apple ', 🍏
'orage ', 🍊
'cactus ', 🌵
'eggplant ', 🍆
'strawberry ', 🍓
'coconut ', 🥥
];
const getRandomItem = () => {
const item = objects[~~(Math.random() * objects.length)].
split(' ');
return {
name: item[0],
icon: item[1],
id: Date.now() + Math.random(),
};
};
return (
<View style={styles.container}>
<Button title="add item" onPress={addItem} />
<FlatList
30
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
data={items}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
</View>
);
};
At the end of the day, FlatList uses ScrollView and View components
as well. What’s the deal then?
Well, the key lies in the logic that is abstracted away within the
FlatList component. It contains a lot of heuristics and advanced Ja-
vaScript calculations to reduce the amount of extraneous renderings
that happen while you’re displaying the data on screen and to make the
scrolling experience always run at 60FPS. Just using FlatList may not
be enough in some cases. FlatList performance optimizations rely on
not rendering elements that are currently not displayed on the screen.
31
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
The most costly part of the process is layout measuring. FlatList has
to measure your layout to determine how much space in the scroll area
should be reserved for upcoming elements.
For complex list elements, it may slow down the interaction with Flat-
List significantly. Every time FlatList approaches to render the next
batch of data, it will have to wait for all the new items to render to
measure their height.
32
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
FlashList recycles the views that are outside of the viewport and re-uses
them for other items. If the list has different items, FlashList uses a
recycle pool to use the item based on its type. It’s crucial to keep the
list items as light as possible, without any side effects, otherwise, it
will hurt the performance of the list.
There are a couple of props that are quite important with FlashList.
First is estimatedItemSize, the approximate size of the list item. It helps
FlashList to decide how many items to render before the initial load
and while scrolling. If we have different-sized items, we can average
them. We can get this value in a warning by the list, if we do not supply
it on the first render and then use it forward. The other way is to use
the element inspector from the dev support in the React Native app.
The second prop is overrideItemLayout, which is prioritized over es-
timatedItemSize. If we have different-sized items and we know their
sizes, it’s better to use them here instead of averaging them.
33
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
34
Use dedicated components for certain layouts
The Ultimate Guide to React Native Optimization
further updates. At the same time, you also save yourself a lot of time
reimplementing the most common UI patterns from the ground up,
sticky section headers, pull to refresh - you name it. These are already
supported by default if you choose to go with FlashList.
35
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 3
This type of ecosystem has many advantages, but also some serious
drawbacks. One of them is that developers can find it hard to choose
from multiple libraries supporting the same use case.
When picking the one to use in the next project, they often research the
indicators that tell them if the library is healthy and well maintained,
such as GitHub stars, the number of issues, contributors, and PRs.
37
Think twice before you pick an
external library The Ultimate Guide to React Native Optimization
The key difference lies in the performance of the mobile devices and
the tooling used for bundling and compiling the application.
Although you will not be able to do much about the device limitations,
you can control your JavaScript code. In general, less code means
faster opening time. And one of the most important factors affecting
the overall size of your code is libraries.
Transpile
This means that all the code that you pull from NPM and import to your
project will be present in your production bundle, loaded into memory,
38
Think twice before you pick an
external library The Ultimate Guide to React Native Optimization
and parsed.That can have a negative impact on the total startup time
of your application.
If you are about to pull a complex library, check if there are smaller al-
ternatives that have the functionality you’re looking for.
39
Think twice before you pick an
external library The Ultimate Guide to React Native Optimization
we can use day.js (only 2Kb) which is substantially smaller and offers
only the functionality that we’re looking for.
For instance, many libraries such as lodash have already split them-
selves into smaller utility sets and support environments where dead
code elimination is unavailable.
Let’s say you want to use lodash map. Instead of importing the whole
library, (as presented here),
40
Think twice before you pick an
external library The Ultimate Guide to React Native Optimization
As a result, you can benefit from the utilities that are a part of the lodash
package without pulling them all into the application bundle.
If you’d like to have constant insight into your dependencies' size impact,
we highly recommend the import-cost VSCode extension. Or using the
Bundlephobia website.
You shouldn’t downplay the importance of choosing the right set of li-
braries. Being more selective with third-party dependencies may seem
irrelevant at first. But all the saved milliseconds will add up to signifi-
cant gains over time.
41
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 4
Always remember
to use libraries
dedicated to the
mobile platform
Always remember
to use libraries
dedicated to the mobile platform The Ultimate Guide to React Native Optimization
In other words – how you optimize the battery consumption both in the
foreground and background can make all the difference.
43
Always remember
to use libraries
dedicated to the mobile platform The Ultimate Guide to React Native Optimization
Mobile libraries were developed within the web environment in the first
place, assuming the capabilities and constraints of the browser. It is
very likely that the result of using a web version of a popular SDK will
result in extraneous CPU and memory consumption.
Certain OSs, such as iOS, are known to be constantly analyzing the re-
sources consumed by the application in order to optimize the battery
life. If your application is registered to perform background activities
and these activities take too much of the resources, the interval for your
application may get adjusted, lowering the frequency of the background
updates that you initially signed up for.
Firebase contains SDKs for the web and mobile – iOS and Android re-
spectively. Each SDK contains support for Realtime Database.
44
Always remember
to use libraries
dedicated to the mobile platform The Ultimate Guide to React Native Optimization
Thanks to React Native, you can run the web version of it without major
problems:
However, this is not what you should be doing. While the above exam-
ple works without issues, it does not offer the same performance as
the mobile equivalent. The SDK itself also contains fewer features –
no surprises here, as web is different and there’s no reason Firebase.
js should provide support for mobile features.
45
Always remember
to use libraries
dedicated to the mobile platform The Ultimate Guide to React Native Optimization
46
Always remember
to use libraries
dedicated to the mobile platform The Ultimate Guide to React Native Optimization
For advanced use cases, you can easily extend React Native with a native
functionality and talk directly to the mobile SDKs. Such an escape hatch
is what makes React Native extremely versatile and enterprise-ready.
It allows you to build features faster on many platforms at once, without
compromising on the performance and user experience – something
other hybrid frameworks cannot claim.
47
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 5
49
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
The number of JavaScript calls that arrive over the bridge is not deter-
ministic and can vary over time, depending on the number of interac-
tions that you do within your application. Additionally, each call takes
time, as the JavaScript arguments need to be stringified into JSON,
which is the established format that can be understood by these two
realms.
For example, when the bridge is busy processing the data, another call
will have to block and wait. If that interaction was related to gestures
and animations, it is very likely that you have a dropped frame – the
operation wasn’t performed causing jitters in the UI.
50
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
Let’s take a JavaScript module that proxies the call straight to the un-
derlying native module.
51
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
That operation will perform without any issues, even though we haven’t
passed the complete list of arguments needed for it to work. The error
will arrive in the next tick when the native side processes the call and
receives an exception from the native module.
In such a scenario, you have lost some time waiting for the exception
that you could’ve checked for beforehand.
ToastExample.show(message, duration);
};
The above is not only tied to the native modules themselves. It is worth
52
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
keeping in mind that every React Native primitive component has its
native equivalent and component props are passed over the bridge
every time there’s a rendering happening – just like you execute your
native method with the JavaScript arguments.
To put this into better perspective, let’s take a closer look at styling
within the React Native apps.
53
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
React Native uses StyleSheet API to pass styles over the bridge most
of the time. That API processes your styles and makes sure they’re
passed only once over the bridge. During the runtime, it substitutes the
value of the style prop with a numeric unique identifier that corresponds
to the cached style on the native side.
As a result, rather than sending a large array of objects each time React
Native is meant to re-render its UI, the bridge has to now deal with an
array of numbers, which is much easier to process and tran fer.
54
Find the balance between native and JavaScript The Ultimate Guide to React Native Optimization
55
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 6
Animate at 60FPS -
no matter what
90 FPS
1.0s
30 FPS
1.0s
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
With React Native, this story is a bit different. If you do not think about
your animations top-down beforehand and choose the right tools to
tackle this challenge, you’re on track to run into dropped frames sooner
or later.
57
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
Mobile users like the interfaces that follow them along and that look
top-notch and ensure the animations are always running smoothly is
a fundamental part that builds such an experience.
// [...]
For more complex use cases, you can use the React Native Rean-
imated library. Its API is compatible with the basic Animated library
and introduces a set of fine-grained controls for your animations with
58
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
Gesture-driven animations
The most desired effect that can be achieved with animations is being
able to control animation with a gesture. For your customers, this is the
most enjoyable part of the interface. It builds a strong sentiment and
makes the app feel very smooth and responsive. Plain React Native is
very limited when it comes to combining gestures with native driven
animations. You can utilize ScrollView scroll events to build things like
a smooth collapsible header.
59
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
return (
<PanGestureHandler onGestureEvent={eventHandler}>
<Animated.View style={animatedStyle}>{props.children}</
Animated.View>
</PanGestureHandler>
);
};
60
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
</View>
);
};
61
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
return (
<View style={styles.container}>
<BottomSheet
callbackNode={gestureCallbackNode}
snapPoints={[50, 400]}
initialSnap={1}
renderHeader={renderHeader}
renderContent={renderInner}
/>
</View>
);
};
62
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
Note: In the near future, you’ll be able to achieve similar behavior with
React itself on a renderer level (with Fabric) using the startTransition
API. Read more about it in the New Architecture chapter.
63
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
const ExpensiveTaskStates = {
notStared: 'not started',
scheduled: 'scheduled',
done: 'done',
};
return (
<View style={styles.container}>
{Platform.OS === 'web' ? (
<Text style={styles.infoLabel}>
❗
InteractionManager works only on native platforms.
Open example on
iOS or Android❗
</Text>
) : (
<>
<Button
title="Start animation and schedule expensive task"
64
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
onPress={startAnimationAndScheduleExpensiveTask}
/>
<Animated.View
style={[styles.box, { width: animationValue.current
}]}>
<Text>Animated box</Text>
</Animated.View>
<Text style={styles.paragraph}>
Expensive task status: {expensiveTaskState}
</Text>
</>
)}
</View>
);
};
This handy React Native module allows you to execute any code after
all running animations are finished. In practice, you can show a place-
holder, wait for the animation to finish, and then render the actual UI.
It would help your JavaScript animations to run smoothly and avoid
65
Animate at 60FPS - no matter what The Ultimate Guide to React Native Optimization
66
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 7
Replace Lottie
with Rive
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
So what other options do we have? Let’s talk about Lottie. A mobile cli-
ent for Android and iOS, which was created by the developers at Airbnb
to leverage LottieFiles, which is JSON-based animation exported using
the plugin BodyMoving from Adobe AfterEffects. They have a pretty
good community with lots of free-to-use animations. Check it out here.
68
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
Source: https://lottiefiles.com/128635-letter-d
If we compare, JSON is 46.2 KB and the GIF is 164.5 KB, which is almost
4 times as much as JSON. We can further reduce the size of JSON
using the Optimized Lottie JSON but that’s a paid solution. Now if we
recall the above approach of having separate GIFs for each onboard-
ing step, we can now use Lottie instead of GIFs since it’s smaller. We
can also use the remote resources of Lottie JSON instead of having it
in a bundle but an extra effort will be required to keep it available for
offline purposes.
We can still do better, but how good will that be if we use a single an-
imation and control it using triggers? For example, if we tap on a but-
ton, the state of the animation changes for the next step. Let’s say we
want to change the size or do other customizations, we can’t do it in
the editor provided by LottieFiles. Instead, we will have to import that
in Adobe AE and then do the adjustments and re-export it as JSON.
We can, however, customize the color for layers in the web editor. Not
everyone has expertise in using Adobe AE and has an animator to
69
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
consult, e.g. if we’re working on our pet project, we will want to adjust
any animation in the web editor.
70
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
return (
<SafeAreaView>
<Rive
ref={riveRef}
resourceName="character"
style={styles.character}
stateMachineName={stateMachineOne}
autoplay
/>
<Button title="Is Running" onPress={onIsRunning} />
<Button title="SideKick" onPress={onSideKick}
color="#3e3e3e" />
</SafeAreaView>
);
};
71
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
Now let’s talk about performance in terms of FPS, CPU, and memory
consumption. We will do a comparison of an animation that is built for
Lottie with the same animation built for Rive. This tweet shows bench-
marks done for the web platform. We’ll extend this by using the same
animations for Rive and Lottie on our React Native app.
Lottie
72
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
Rive
Both images show a tiny window right above the rocket with details of
FPS on both the UI and JS thread. The results show that the Rive ani-
mation runs almost at 60FPS whereas Lottie runs at 17FPS.
Now let’s focus on the right part of both images, which is the Memory
consumption detailed view. If we look closely, there are mainly three
portions: Java, Native, and Graphics. Java represents the memory
allocated for Java or Kotlin code. The Native represents the memory
allocated from C or C++ code. The graphics represent the memory
used for the graphics buffer queues to display pixels on the screen. In
Java, Lottie uses almost 23 MB and Rive uses almost 12 MB of RAM.
In Native, Lottie uses 49 MB and Rive uses 25 MB of RAM. In Graphics,
Lottie consumes 123 MB, whereas Rive uses 184 MB of RAM. The total
memory consumption of Lottie is 246 MB and Rive is 276 MB.
73
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
The results show that Rive outperforms Lottie in all departments ex-
cept Graphics. The end user expects the app to run at 60FPS to enjoy a
smooth user experience. If one has to do a trade-off between memory
consumption and FPS, they might go with FPS as most of the devices
have enough memory to exercise the app’s needs.
Rive’s state machines give designers the power to think as if they were
coding and structure the state machine for an animation that will inter-
act after being given a certain input. Now the developer can use that
animation and implement the interactivity firing the inputs on the ani-
mation and be done with it. If this animation needs to be changed with
the same inputs, the dev only needs to replace the animation source
and that’s it. More info here
Almost 18.7% of people uninstall the app due to storage issues. This
hurts the company’s ROI. Developers should always pay attention to
reducing the bundle size and the storage utilized by their app. In a tweet
74
Replace Lottie With Rive The Ultimate Guide to React Native Optimization
by Rive’s CEO, the Lottie file was around 24.37 KB and the same Rive
file was around 2 KB. At the end of the day, each KB saved adds up to a
reduced app size. We always want to choose a library that best fulfills
our needs by providing a better developer experience, ease of API, and
a smooth experience for the end user.
75
The Ultimate Guide to React Native Optimization
PA RT 1 | C H A P T E R 8
Optimize your
app’s JavaScript
bundle
Optimize your app’s JavaScript bundle The Ultimate Guide to React Native Optimization
Re.Pack
Re.Pack is a Webpack-based toolkit to build your React Native appli-
cation with full support of the Webpack ecosystem of loaders, plugins,
and support for various features like symlinks, aliases, code splitting,
etc. Re.Pack is the successor to Haul, which served a similar purpose
but balanced a different set of tradeoffs and developer experience.
The ecosystem part of Webpack is crucial for many developers, since
it’s the most popular bundler of the web, making the community be-
hind loaders and plugins its key advantage. Thanks to that pluggability,
it provides ways to improve the build process and Webpack’s overall
77
Optimize your app’s JavaScript bundle The Ultimate Guide to React Native Optimization
performance. At least for the parts that are not connected to the inter-
nal module graph building and processing. Such parts would be, e.g.
JavaScript and TypeScript transpilation or code minification. You can
replace Babel transpiler and Terser minifier with faster alternatives like
ESBuild thanks to the esbuild-loader or swc with swc-loader.
Another Webpack feature that helps our apps achieve better perfor-
mance is reducing the amount of code in the final bundle with tree
shaking. Tree shaking is a dead code elimination technique done by
analyzing the import and export statements in the source code and
determining which code is actually used by the application. Webpack
will then remove any unused code from the final bundle, resulting in a
smaller and more efficient application. The code that’s eligible for tree
shaking needs to be written in ECMAScript modules (import and export
statements) and mark itself as side-effect free through package.json
“sideEffects: false” clause.
The very first issue on Metro bundler’s GitHub is about symlink support.
It remains open today, as there are various reasons Metro is blocked
to introduce this functionality with the desired DX. There are ways to
mitigate that particular shortcoming, but they require extra configura-
tion. Webpack, on the other hand, as virtually any other bundler, and
doesn’t have this issue. Re.Pack uses the Webpack bundler under the
hood so it provides symlinks functionality out-of-the-box. Which could
be invaluable from the developer experience point of view of some
workflows like monorepos.
78
Optimize your app’s JavaScript bundle The Ultimate Guide to React Native Optimization
first place. Directly from a remote server or a CDN. Now this can help
you with reducing not only the initial load time, but also the precious
app size.
All these configurations and flexibility affect the build process. The
build speed is a little bit longer than the default Metro bundler due to
customization options. Also, the Fast Refresh functionality is limited
compared to the Metro bundler. The Hot Module Replacement and Re-
act Refresh features require the full application reloaded with Webpack
and Re.Pack, but they are supported by Metro.
If you don’t need the huge customization that the Webpack ecosystem
offers or don’t plan to split your app code, then you may as well keep
the default Metro bundler.
react-native-esbuild
One of the main benefits of react-native-esbuild is fast builds. It uses
the ESBuild bundler under the hood which has huge improvements in
bundling performance even without caching. It also provides some
features like tree shaking and is much more configurable compared to
the Metro bundler. ESBuild has its own ecosystem with plugins, cus-
tom transformers, and env variables. This loader is enabled by default
for .ts, .tsx, .mts, and .cts files, which means ESBuild has built-in
support for parsing TypeScript syntax and discarding the type annota-
tions. However, ESBuild does not do any type checking so you will still
need to run type check in parallel with ESBuild to check types. This is
not something ESBuild does itself.
Unfortunately, react-native-esbuild has some tradeoffs, so it is very im-
portant to select the right bundler by paying attention to them as well.
79
Optimize your app’s JavaScript bundle The Ultimate Guide to React Native Optimization
rnx-kit
An interesting extension to Metro is Microsoft’s rnx-kit. It is a package
with a huge variety of React Native development tools. It also has a
custom bundler that works on top of the Metro bundler enhancing it.
As you already know, Metro does not support symlinks. rnx-kit provides
the ability to fully work with symlinks. One more benefit compared to
Metro is the tree shaking functionality out-of-the-box.
Metro supports TypeScript source files, but it only transpiles them to
JavaScript. Metro does not do any type-checking. rnx-kit solves this
problem. Through the configuration, you can enable type-checking.
Warnings and errors from TypeScript appear on the console.
Also, rnx-kit provides duplicate dependencies and cyclic dependencies
detection out-of-the-box. This could be very useful to reduce the size
of the bundle which leads to better performance and prevents cyclic
dependencies issues.
80
Optimize your app’s JavaScript bundle The Ultimate Guide to React Native Optimization
81
The Ultimate Guide to React Native Optimization
PA RT 2
All that proves that React Native is developing at a really healthy pace.
Contributions made by both the community and Meta enable more
and more advanced use cases of the framework. A great example of
that is Hermes – an entirely new JavaScript engine built and designed
specifically for React Native and Android. Hermes aims to replace the
JavaScriptCore, previously used on both Android and iOS. It also brings
a lot of enterprise-grade optimizations by improving your Android ap-
plication’s performance, start-up time, and overall size reduction.
In this section, we will show you some of the features you can turn on
right now to start your optimization process. We also encourage you
to keep track of all the new React Native features to make sure you use
the framework to its full potential.
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 1
Every day, developers from all around the world introduce new features,
critical bug fixes, and security patches. On average, each release in-
cludes around 500 commits.
Over the years, React Native has grown significantly, thanks to open-
source contributors and Meta’s dedication to improving the ecosystem.
Here are some highlighted crucial features that have been introduced
to React Native over the course of its releases:
Fast Refresh
To improve the developer experience and velocity, the React team in-
troduced a feature called Fast Refresh to React Native. This lets you
quickly reflect the code on the device, whenever you save the file instead
of building or reloading the app. It is smart enough to decide when to
do a reload after we fix an error or just render otherwise.
A tip here: the local state of functional components and hooks is pre-
served by default. We can override this by adding a comment to that
84
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
Auto-Linking
Whenever we add native code to our React Native app as a dependency,
it needs to be linked. Previously linking was done manually or using
react-native link dep-name. React Native CLI introduced auto-linking so
the devs didn’t need to link a library themselves. After a couple of years,
when the re-architecture of React Native was released to the community,
a need arose to auto link fabric components and turbo modules, which
was handled gracefully by the CLI team. They released an update to
the community to help the developer experience and velocity.
Flipper
Introduced as a recommended way of debugging React Native apps,
Flipper is loaded with awesome tools such as ReactDevtools, Network
Inspector, Native Layout Inspector, and others. We can also view the
Metro and Device logs right in Flipper. The community has appreci-
ated such a feature, and one example of that is a performance plugin
that we can install in Flipper to measure the performance of the React
Native apps.
LogBox
React Native redesigned its error and warning handling system. They
had to do a ground-up redesign of its logging system and the developer
experience is much better because of it. Developers can easily trace
the cause of an error using code frames and component stacks. Syn-
tax error formatting helps to understand the issue more quickly with
the aid of syntax highlighting. Log Notifications show warnings and
logs at the bottom of the screen instead of covering the whole screen.
Hermes
A new JS engine created by Meta to improve the performance of React
Native apps in terms of CPU usage, memory consumption, app size,
and Time To Interactive (TTI). Initial support was launched for Android
85
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
devices, but after two years, support was extended to iOS as well. After
a couple of months, the previously used garbage collector for Hermes
GenGC was replaced with a new one called Hades - a concurrent gar-
bage collector. The Meta team saw improvements of CPU-intensive
workloads by 20-50%. Later on, the team decided to ship a bundled
Hermes instead of downloading it from NPM. This was done to avoid
confusion between what version of Hermes is compatible with React
Native. Also, both Hermes and React Native use the same JSI code
which makes it hard to maintain. Now whenever a version of React Na-
tive is released, a version of Hermes can be released as well, making
sure that both are fully compatible.
New Architecture
This one has its own chapter.
In the React Native ecosystem, it’s common that libraries are not back-
ward- compatible. New features often use goodies not available in the
previous versions of the framework. This means that if your application
runs on an older version of React Native, you are eventually going to
start missing out on the latest improvements.
@react-native-community/cli react-native
^10.0.0 ^0.71
^9.0.0 ^0.70
^8.0.0 ^0.69
^7.0.0 ^0.64
^6.0.0 ^0.68
^5.0.0 ^0.65,^0.66,^0.67
^3.0.0 ^0.61
^2.0.0 ^0.60
^1.0.0 ^0.59
86
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
That’s why keeping up with the newest React Native upgrades are the
only way to go.
For instance, it may turn out that the modules and components you
used in your code are no longer part of the react-native core.
87
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
The actual amount of work will depend on the number of changes and
your base version. However, the steps presented in this section can be
applied to every upgrade, regardless of the state of your application.
88
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
Native since the last time you upgraded your local version.
89
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
Having a better overview of the changes will help you move faster and
act with more confidence.
Thanks to that, you will not only be aware of the changes, but you will
also understand the reasoning behind them. And you will be ready to
open up your project and start working on it.
90
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
The first step is to bump the React and React Native dependencies to
the desired versions and perform the necessary changes (including
breaking changes). To do so, you can look up the suggestions provided
by React Native Upgrade Helper and apply them manually. Once it’s
completed, make sure to reinstall your node_modules.
91
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
If the error occurs during the build time, bumping the dependency to
its latest version usually makes it work. But it may not always be the
case. To make sure the version of React Native you’re upgrading to
is compatible with your dependencies, use the align-deps project by
Microsoft developers. It allows you to keep your dependencies on the
right version based on the requirements and by leveraging the presets
of rules. It also has a CLI, so you can wire it up to your CI and ensure
that no one in your repo or monorepo will inadvertently introduce in-
compatible versions of packages and break the app.
Once your application builds, you are ready to check the changelog and
make yourself familiar with the JavaScript changes that happened to
the public API. If you overlook this step, it can result in runtime excep-
tions. Using Flow or TypeScript should guarantee that the changes
were applied properly.
As you can see, there is no magic trick that would fix all the errors and
92
Always run the latest version of React Native to access the new features The Ultimate Guide to React Native Optimization
Upgrades like this are really critical to keeping your users satisfied. Af-
ter all, they would be disappointed if the app started to crash with the
newer version of the operating system or disappeared from the App
Store. There might be some additional workload associated with every
release, but staying up to date will pay back with happier users, more
stable apps, and a better development experience.
93
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 2
How to debug
faster and better
with Flipper
How to debug faster and better with Flipper
The Ultimate Guide to React Native Optimization
When it comes to debugging native code, you have to use the tools
built into Android Studio and Xcode.
95
How to debug faster and better with Flipper
The Ultimate Guide to React Native Optimization
Another inconvenience is the fact that you cannot easily debug network
requests with the Chrome Debugger (it needs additional setup and still
has its limitations). In order to debug all possible requests, you have
to open a dedicated network debugger using the emulator’s developer
menu. However, its interface is very small and inconvenient due to the
size of the emulator’s screen.
96
How to debug faster and better with Flipper
The Ultimate Guide to React Native Optimization
From the developer menu, you can access other debugging utilities,
such as layout inspector or performance monitor. The latter is relatively
convenient to use, as it’s displaying only a small piece of information.
However, employing the former is a struggle because of the limited
workspace it provides.
97
How to debug faster and better with Flipper
The Ultimate Guide to React Native Optimization
Source: https://fbflipper.com/docs/features/react-native
What’s more, Flipper lets you preview logs from native code and track
native crashes, so you don’t have to run Android Studio or Xcode to
check what is happening on the native side!
98
How to debug faster and better with Flipper
The Ultimate Guide to React Native Optimization
What’s most exciting is that all the needed utilities are placed in one
desktop app. This minimizes context switches. Without Flipper, a de-
veloper debugging an issue related to displaying the data fetched from
the backend had to use the Chrome Debugger (to preview logs), in-em-
ulator network requests debugger, and probably in-emulator layout
inspector, or a standalone React Devtools app. With Flipper, all those
tools are available as built-in plugins. They are easily accessible from
a side panel and have similar UI and UX.
99
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 3
Avoid unused
native dependencies
Avoid unused
native
dependencies The Ultimate Guide to React Native Optimization
In our React Native apps, we often rely on dependencies that load Ko-
tlin, Java, Swift, ObjectiveC, JavaScript, and recently more often, even
C++. Those dependencies are declared in the package.json file, which
allows for a JavaScript bundler to correctly discover and, well, bundle
their JS parts into the final application. It may be counterintuitive at
first, but this declaration in the JavaScript toolchain influences the na-
tive side as well. And the reason for that is the “autolinking” feature of
the React Native CLI.
101
Avoid unused
native
dependencies The Ultimate Guide to React Native Optimization
Unused dependencies
* lottie-react-native
* react-native-gesture-handler
* react-native-maps
* react-natave-reanimated
* react-native-video
* react-native-webview
Unused devDependencies
* @babel/core
* @babel/runtime
* @react-native-community/eslint-config
* @tsconfig/react-native
* @types/jest
* @types/react-test-renderer
* babel-jest
* jest-circus
* metro-react-native-paper-preset
* typescript
102
Avoid unused
native
dependencies The Ultimate Guide to React Native Optimization
are relying on some native code. Now we have to remove them and it’s
done! In the example app, removing unused dependencies from the
screenshot above occurred with the following result in the bundle size:
Comparision of bundle sizes before and after removing unused native dependencies
It’s worth mentioning that on the Android device, there was a noticeable
improvement in the Time to Interactive, which was reduced by 17% in
this case.
You may be wondering how we can measure the TTI. There are a few
ways to do it. Whichever you choose, remember to always measure
on a release version of the app when dealing with absolute numbers.
One way is to use a stopwatch and measure the time the app took to
show the first screen, as shown here. It’s entirely manual, but it will
often get the job done for one-off measurements.
103
Avoid unused
native
dependencies The Ultimate Guide to React Native Optimization
We can also use App launch from Xcode instruments. All you need is to
install a release build through profiling to your real device. Then select
App Launch from the window that will appear automatically once the
build is installed. Hit the record button, and once the app has launched,
104
Avoid unused
native
dependencies The Ultimate Guide to React Native Optimization
It’s worth mentioning that there are lots of third-party tools helping de-
velopers to gain a bunch of performance information from apps already
submitted to Google Play and App Store. The most popular are Firebase
Performance Monitoring, Sentry, and DataDog. The key advantage of
using one of these tools is gaining data about performance from many
different devices.
105
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 4
Optimize your
application startup
time with Hermes
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
There is no single definition of the startup time. It’s because there are
many different stages of the loading phase that can affect how “fast”
or “slow” the app feels. For example, in the Lighthouse report, there are
eight performance metrics used to profile your web application. One
of them is Time to Interactive (TTI), which measures the time until the
application is ready for the first interaction.
There are quite a few things that happen from the moment you press
the application icon from the drawer for the first time.
1 2 3 4
Native Render JS Render JS Init + Require Native Init
The loading process starts with a native initialization which loads the
JavaScript VM and initializes all the native modules (1 in the above
diagram). It then continues to read the JavaScript from the disk and
loads it into the memory, parses, and starts executing (2 in the above
diagram). The details of this operation were discussed earlier in the
section about choosing the right libraries for your application.
107
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
In the next step, React Native starts loading React components and
sends the final set of instructions to the UIManager (3 in the above
diagram). Finally, the UIManager processes the information received
from JavaScript and starts executing the native instructions that will
result in the final native interface (4 in the above diagram).
As you can see in the diagram below, there are two groups of operations
that influence the overall startup time of your application.
1 2 3 4
Native Render JS Render JS Init + Require Native Init
The first one involves the first two operations (1 and 2 in the diagram
above) and describes the time needed for React Native to bootstrap
(to spin up the VM and for the VM to execute the JavaScript code). The
other one includes the remaining operations (3 and 4 in the diagram
above) and is associated with the business logic that you have created
for your application. The length of this group is highly dependent on the
number of components and the overall complexity of your application.
If you have not measured the overall startup time of your application
or have not played around with things such as Hermes yet - keep on
reading.
108
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
109
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
Today Hermes is still small enough (~2 MB) to provide significant im-
provements to apps’ TTI and gives us a set of features rich enough to
be used in most of the apps out there.
Bytecode precompilation
Typically, the traditional JavaScript VM works by parsing the JavaScript
source code during the runtime and then producing the bytecode. As
a result, the execution of the code is delayed until the parsing com-
pletes. It is not the same with Hermes. To reduce the time needed for
the engine to execute the business logic, it generates the bytecode
during the build time.
110
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
Transpile
It can spend more time optimizing the bundle using various techniques
to make it smaller and more efficient. For example, the generated by-
tecode is designed in a way so that it can be mapped in the memory
without eagerly loading the entire
file. Optimizing that process brings significant TTI improvements as
I/O operations on mobile devices tend to increase the overall latency.
No JIT
The majority of modern browser engines use just-in-time (JIT) com-
pilers. It means that the code is translated and executed line-by-line.
However, the JIT compiler keeps track of warm code segments (the
ones that appear a few times) and hot code segments (the ones that
run many times). These frequently occurring code segments are then
sent to a compiler that, depending on how many times they appear in
the program, compiles them to the machine code and, optionally, per-
forms some optimizations.
111
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
On the other hand, JIT engines decrease the TTI as they need time to
parse the bundle and execute it in time. They also need time to “warm
up”. Namely, they have to run the code a couple of times to detect the
common patterns and begin to optimize them.
If you want to start using Hermes on Android, make sure to turn the
enableHermes flag in android/app/build.gradle to true:
project.ext.react = [
entryFile: "index.js",
enableHermes: true
]
use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and
then install pods
:hermes_enabled => true
)
In both cases, whenever you switch the Hermes flag, make sure to re-
build the project according to instructions provided in the native files.
Once your project is rebuilt, you can now enjoy a faster app boot time
and likely smaller app size.
112
Optimize your
application startup time with Hermes The Ultimate Guide to React Native Optimization
Apart from that, you can also look into other significant improvements
shipped by the Meta team. To do so, get familiar with their write-up on
React Native performance. It is often a game of gradual improvements
that make all the difference when applied at once. The React Native
core team has created a visual report on benchmarking between stock
RN and Hermes-enabled RN: see here.
Doing so will help your application stay on top of the performance game
and let it run at a maximum speed.
113
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 5
Optimize your
Android application’s
size with these
Gradle settings
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
Should you really care about app size in the era of super-fast mobile
internet and WiFi access everywhere? Why does a bundle size grow so
rapidly? We will answer those questions in this section. But first, let’s
have a look at what a typical React Native bundle is made of.
React Native offers some optimizations that allow you to improve the
structure of the bundle and its overall size. But they are disabled by
default.
If you are not using them effectively, especially when your application
grows, you are unnecessarily increasing the overall size of your appli-
cation in bytes. That can have a negative impact on the experience of
115
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
Right now, there are still markets where every megabyte of traffic has
its price. In those regions, the application’s size directly impacts the
conversion, and the installation/ cancellation ratio increases along
with the app size.
Install rate, varying app size
Source: https://segment.com/blog/mobile-app-size-effect-on-downloads/
116
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
It is also a common belief that every well crafted and carefully designed
application not only provides a beautiful interface but is also optimized
for the end device. Well, that is not always the case. And because the
Android market is so competitive, there is a big chance that a smaller
alternative to those beautiful yet large apps is already gaining more
traction from the community.
Now, let’s move to the last factor worth mentioning in this context –
device storage.
Apps usually end up taking up more space after the installation. Some-
times they may even not fit into the device's memory. In such a situation,
users may decide to skip installing your product if that would mean
removing other resources such as applications or images.
117
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
While developing your application, Gradle generates the APK file that
can be installed on any of the mentioned CPU architectures. In other
words, your APK (the file outputted from the build process) is actually
four separate applications packaged into a single file with .apk exten-
sion. This makes testing easier as the application can be distributed
onto many different testing devices at once.
Unfortunately, this approach has its drawbacks. The overall size of the
application is now much bigger than it should be as it contains the files
required by all architectures. As a result, users will end up downloading
extraneous code that is not even compatible with their phones.
App Bundle is a publishing format that allows you to contain all com-
piled code and resources. It’s all due to the fact that Google Play Store
Dynamic Delivery will later build tailored APKs depending on the end
users’ devices.
To build App Bundle, you have to simply invoke a different script than
usual. Instead of using ./gradlew assembleRelease, use ./gradlew
bundleRelease, as presented here:
The main advantage of the Android App Bundle over builds for multiple
cd android
./gradlew bundleRelease
architectures per CPU is the ease of delivery. After all, you have to ship
only one artifact and Dynamic Delivery will do all the magic for you. It
also gives you more flexibility on supported platforms.
You don’t have to worry about which CPU architecture your end user’s
device has. The average size reduction for an app is around 35%, but in
118
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
some cases, it can be even cut in half, according to the Android team.
Source: https://medium.com/google-developer-experts/exploring-the-android-app-bundle-ca16846fa3d7
119
Optimize your
Android
application’s size with these Gradle
settings The Ultimate Guide to React Native Optimization
Reducing redundant text from svg and compressing png images can
save some bytes when your project has already too many of them.
By striving for a smaller APK size, you will do your best to reduce the
download cancellation rate. Also, your customers will benefit from a
shorter Time To Interactive and be more inclined to use the app more
often.
Finally, you will demonstrate that you care about every user, not only
those with top-notch devices and fast internet connections. The big-
ger your platform gets, the more important it is to support those minor
groups, as every percent of users translates into hundreds of thousands
of actual users. If you’d like to learn more about optimizing Android,
check the Android Profiling chapter.
120
The Ultimate Guide to React Native Optimization
PA RT 2 | C H A P T E R 6
Experiment with
the New Architecture
of React Native
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
The bridge is still working fine for most use cases. However, when we
start to send a lot of data over the bridge, it may become a bottleneck
for our app. This problem can be seen when rendering a lot of com-
ponents in a long list. In the case when the user scrolls fast, there will
be a blank space caused by the communication between the JS and
native sides being asynchronous. Essentially what happens is that we
are having a “traffic jam” on our bridge with objects waiting to be seri-
alized. The same issue with the bridge being “overloaded” can be seen
in native modules sending a lot of data back and forth.
This bottleneck is the main thing that the new rendering system is try-
ing to solve. However, not everything about the New Architecture is as
good as it seems. We will also get into the drawbacks that it brings.
122
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
Codegen
A code generation tool that makes JS a source of truth by automating
the compatibility between the JS and native sides. It allows for writing
statically typed JS (called JS Spec) which is then used to generate the
interface files needed by Fabric native components and TurboMod-
ules. Spec consists of a set of types written in TypeScript or Flow that
defines all the APIs provided by the native module. Codegen ensures
type-safety as well as compile-time type safety, which means smaller
code and faster execution as both realms can trust each other around
validating the data every time. To find out more about it, refer to the docs.
JSI
JSI is the foundation of the New Architecture, a C++ API for interacting
with any JS engine. In contrast to the bridge which was asynchronous,
123
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
Fabric
Fabric is React Native's new concurrent rendering system, a conceptual
evolution of the legacy render system. The core principle is to unify
more render logic in C++ to better leverage interoperability between
platforms. Host Components like View, Text, etc. are now lazily initial-
ized, resulting in faster startups.
Fabric allows us to take advantage of the features introduced in React
18. Instead of the bridge, we can use JSI which is synchronous by de-
fault. For the time being, React Native is not removing the bridge com-
pletely; it’s still available so developers can gradually adopt the New
Architecture and JSI. However, using the bridge needs to be explicitly
stated (by importing it), so modules not yet migrated won’t work. Meta
is planning to soon allow apps to run in a completely bridge-less mode.
TurboModules
This is a new way of writing native modules that also leverages the
power of JSI, allowing for synchronous, and an order of magnitude
faster data transfer from native to JS and vice versa. It is a rewrite
of the communication layer between JavaScript and platform native
modules like Bluetooth, Biometrics, etc. It also allows for writing native
code for both platforms using C++ and introduces the lazy loading of
modules to speed up your app startup time.
124
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
Performance
Due to the synchronous nature of the New Architecture, there will be
some performance improvements while communicating with the native
side. However, not every scenario proves this, in some of the bench-
marks the New Architecture performance is worse.
Meta’s goal was not to make the New Architecture X times faster than
the old one. They wanted to get rid of the major bottlenecks blocking
them from adding new features to the framework. The migration of the
Facebook app took over a year and they haven’t noticed any significant
performance improvements or regressions that are perceivable by the
end user. However, this doesn’t mean that performance improvements
won’t come in the future. Now that they reworked internals, they have
a great foundation to build upon.
125
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
126
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
The official response from the React Native team is that their internal
benchmarks while rolling out the New Architecture to users was neutral
across all React Native surfaces in the Facebook app on both Android
and iOS. As stated by Samuel Susla in this discussion thread, “In the
last years, we conducted dozens of tests in production on millions of
devices to assure performance was neutral.”
Future readiness
This allows your app to leverage Concurrent React features, which im-
prove UI responsiveness, Suspense for data fetching to handle complex
UI loading schemes, and any further React innovations built on top of
its new concurrent engine introduced in React 18.
Here is a discussion about the startTransition (React 18) feature being
used in React Native, however, the author of the post didn’t see any
benefits compared to the web: https://github.com/reactwg/react-na-
tive-new-architecture/discussions/94
127
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
text UI will be updated but the UI for the container will still remain green
until the transition is completed and the color will change to red due
to the new UI being rendered. That’s the magic of React’s concurrent
rendering.
To understand it better, assume that wrapping an update in startTran-
sition renders it in a different universe. We don’t see that universe
directly but we can get a signal from it using the isPending variable
returned from the useTransition hook. Once the new UI is ready, both
universes merge together to show the final UI.
return (
<View>
<Text>Non urgent update value: {isPending ? 'PENDING' :
value}</Text>
<View style={[styles.container, backgroundStyle]}>
{dummyData.map((_, index) => (
<View key={index} style={styles.item} />
))}
</View>
</View>
);
};
128
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
129
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
There are some other noticeable features in React18 that you might
want to check out by playing with the linked sandboxes from React’s of-
ficial website. See useDeferredValue and startTransition with Suspense.
The Meta team has a dedicated capacity to help the community solve
their app and library problems regarding New Architecture adoption.
It’s worth starting your migration early so you can identify the problems
and blockers that your app may be facing.
130
Experiment with the New
Architecture
of React Native
The Ultimate Guide to React Native Optimization
2023 is the best time to plan and start executing the adoption of the
New Architecture. You can get a head start with the fine support from
the Meta engineers in solving your app’s build and runtime issues that
prevent you from migration.
131
The Ultimate Guide to React Native Optimization
PA RT 3
React Native plays really well in such environments. For example, one
of its biggest selling points is that it allows you to ship updates to your
applications without undergoing the App Store submission. They’re
called Over-the-Air (OTA) updates.
The question is: is your application ready for that? Does your develop-
ment pipeline accelerate the development and shipping features with
React Native?
Most of the time, you would like the answer to be simply yes. But in
reality, it gets complicated.
PA RT 3 | C H A P T E R 1
But beware, writing a test may be a challenging task on its own, espe-
cially if you lack experience. You might end up with a test that doesn’t
have a good coverage of your features. Or only to test positive behavior,
134
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
Such scenarios may seem pretty rare, but they happen. Then, your team
may become so afraid of having another regression and crash that it
will lose its velocity and confidence.
Most of the mobile apps out there don’t need a full test coverage of
the code they write.
135
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
The exceptions are situations in which the client requires full coverage
because of the government regulations they must abide by. But in such
cases, you’re probably already aware of the problem.
It’s crucial for you to focus your time on testing the right thing. Learn-
ing to identify business-critical features and capabilities is usually
more important than writing a test itself. After all, you want to boost
confidence in your code, not write a test for the sake of it. Once you
do that, all you need to do is decide on how to run it. You have quite a
few options to choose from.
136
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
137
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
JavaScript testing
Writing tests for utility functions should be pretty straightforward. To
do so, you can use your favorite test runner. The most popular and rec-
ommended one within the React Native community is Jest. We’ll also
be referring to it in the following sections.
For testing React components, you need more advanced tools though.
Let’s take the following component as an example:
return (
<ScrollView>
{questions.map((q, index) => {
return (
<View key={q}>
<Text>{q}</Text>
<TextInput
accessibilityLabel="answer input"
onChangeText={(text) => {
setData((state) => ({
...state,
[index + 1]: { q, a: text },
}));
}}
/>
</View>
);
})}
<TouchableOpacity onPress={() => onSubmit(data)}>
<Text>Submit</Text>
</TouchableOpacity>
</ScrollView>
);
};
138
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
That’s why the community around React Native came out with helper
libraries, such as React Native Testing Library, providing us with a good
set of helpers to productively write your high-quality tests.
A great thing about this library is that its API forces you to avoid test-
ing the implementation details of your components, making it more
resilient to internal refactors.
139
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
render(<QuestionsBoard questions={allQuestions}
onSubmit={mockFn} />);
fireEvent.changeText(answerInputs[0], 'a1');
fireEvent.changeText(answerInputs[1], 'a2');
fireEvent.press(screen.getByText('Submit'));
expect(mockFn).toBeCalledWith({
1: { q: 'q1', a: 'a1' },
2: { q: 'q2', a: 'a2' },
});
});
You first render the QuestionsBoard component with your set of ques-
tions. Next, you query the tree by label text to access an array of ques-
tions, as displayed by the component. Finally, you set up the right
answers and press the submit button.
If everything goes well, your assertion should pass, ensuring that the
verifyQuestions function has been called with the right set of argu-
ments.
140
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
They may help you and your team quickly add coverage to the project.
And at the same time, snapshots make adding low-quality and hard-to-
maintain tests too easy. Using helper tools like eslint-plugin-jest with
its no-large-snapshots option, or snapshot-diff with its component
snapshot comparison feature for focused assertions, is a must-have
for any codebase that leverages this testing technique.
E2E tests
The cherry on top of our testing pyramid is a suite of end-to-end tests.
It’s good to start with a so-called “smoke test” – a test ensuring that
your app doesn’t crash on the first run. It’s crucial to have a test like this,
as it will help you avoid sending a faulty app to your users. Once you’re
done with the basics, you should use your E2E testing framework of
choice to cover the most important functionalities of your apps.
These can be, for instance, logging in (successfully or not), logging out,
accepting payments, and displaying lists of data you fetch from your
or third-party servers.
141
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
For the purpose of this section, we’ll be looking at Detox – the most
popular E2E test runner within the React Native community and a part
of the React Native testing pipeline. With Detox, you will be able to en-
sure that your framework of choice is supported by the latest React
Native versions. That is especially important in the context of future
changes that may happen at a framework level.
Before going any further, you have to install Detox. This process re-
quires you to take some additional “native steps” before you’re ready
to run your first suite. Follow the official documentation as the steps
are likely to change in the future.
Once you have successfully installed and configured Detox, you’re ready
to begin with your first test.
await element(by.text(allQuestions[0])).toBeVisible();
});
This quick snippet shown above would ensure that the first question
is displayed.
142
Run tests for key pieces of your app
The Ultimate Guide to React Native Optimization
Before that assertion is executed, you should reload the React Native
instance to make sure that no previous state is interfering with the
results.
143
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 2
Have a working
Continuous
Integration (CI)
in place
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
What is equally important is how quickly you detect the potential re-
gressions and whether finding them is a part of your daily development
lifecycle. In other words – it all comes down to the feedback loop.
For better context, let’s take a look at the early days of the development
process. When you’re starting out, your focus is on shipping the first
iteration (MVP) as fast as possible. Because of that, you may overlook
the importance of the architecture itself. When you’re done with the
changes, you submit them to the repository, letting other members of
your team know that the feature is ready to be reviewed.
‘main’
‘main’ ‘main’
An example of a workflow on Github, where changes are proposed in the form of a PR.
145
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
146
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
Using a CI service, you not only test your code but also build a new ver-
sion of the documentation for your project, build your app, and distrib-
ute it among testers or releases. This technique is called Continuous
Deployment and focuses on the automation of releases. It has been
covered in more depth in this section.
It is the default CI provider for React Native and all projects created by
its community. In fact, there is actually an example project demonstrat-
ing the use of CI with React Native. You can learn more about it here.
We will employ it later in this section to present different CI concepts.
147
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
Let’s take a look at a sample configuration file for CircleCI, taken from
the mentioned React Native example:
version: 2.1
jobs:
android:
working_directory: ~/CI-CD
docker:
- image: reactnativecommunity/react-native-android
steps:
- checkout
- attach_workspace:
at: ~/CI-CD
- run: npm i -g envinfo && envinfo
- run: npm install
- run: cd android && chmod +x gradlew && ./gradlew
assembleRelease
workflows:
build_and_test:
jobs:
- android
Example of .circleci/config.yml
148
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
For example, you may want to use a Node container if you need to run
only your React unit tests. As a result, the container will be smaller,
have fewer dependencies, and will install faster. If you want to build a
React Native application in the cloud, you may choose a different con-
tainer, e.g. with Android NDK/SDK or the one that uses OS X to build
Apple platforms.
TS, Linter
You can also modify the jobs execution schedule by adding filters, so,
for instance, a deploy job will only run if the changes in the code refer
to the main branch.
149
Have a working Continuous Integration (CI) in place
The Ultimate Guide to React Native Optimization
You can define many workflows for different purposes, e.g. one for
tests that would run once a PR is opened, and the other to deploy the
new version of the app. This is what React Native does to automatically
release its new versions every once in a while.
GitHub UI reporting the status of CircleCI jobs, an example taken from React Native repository
By spotting errors beforehand, you can reduce the effort needed to review the
PRs and protect your product against regressions and bugs that may directly
decrease your income.
150
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 3
However, testing and development are only a part of the activities that
you have to perform when working on a product. Another important
step is the deployment – building and distributing the application to
production. Most of the time, this process is manual.
The deployment takes time to set up and is far more complex than
just running tests in the cloud. For example, on iOS, Xcode configures
many settings and certificates automatically. This ensures a better de-
veloper experience for someone who’s working on a native application.
Developers who are used to such an approach often find it challenging
to move the deployment to the cloud and set up such things as certif-
icates manually.
The biggest downside of the manual approach is that it takes time and
doesn’t scale. In consequence, teams that don’t invest in the improve-
ments to this process end up releasing their software at a slower pace.
152
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
The first way is to write a set of scripts from scratch by interacting with
xcode and gradle directly. Unfortunately, there are significant differ-
ences between the tooling of Android and iOS and not many developers
have enough experience to handle this automation. On top of that, iOS
is much more complicated than Android due to advanced code signing
and distribution policies. And as we have said before, if you are doing
it manually, even Xcode cannot help you.
153
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
After you have successfully built your binaries, it is time to deploy them
to their destination.
Again, you can either upload the files to the desired service (e.g. App
Store) manually or use a tool that will take care of that for you. For the
same reasons as before, we prefer to use an existing solution - in this
case, AppCenter by Microsoft.
AppCenter is a cloud service with tooling for the automation and de-
ployment of your application. Its biggest advantage is that many of
the settings can be configured from the graphical interface. It is much
easier to set up the App Store and Play Store deployments this way,
rather than working with uploads from the command line.
For the purpose of this section, we will use fastlane and AppCenter in
CircleCI pipelines to fully automate the process of app delivery to the
final users.
Setting up Fastlane
Before going into the details for Android and iOS, you have to make
sure that fastlane has been installed and configured on your devices.
154
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
Next, you have to run the init command within the React Native project.
We will run the fastlane command twice from each native folder. This
is because React Native is actually two separate apps at a low level.
As a result, this command will generate setup files in both ios and an-
droid folders. The main file in each folder would be called Fastfile and
it’s where all the lanes will be configured. In the fastlane nomenclature,
a lane is just like a workflow - a piece that groups low-level operations
that deploy your application.
Our lane uses the gradle action to first clean the build folder, and then
assemble the APK with signature based on passed params.
155
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
default_platform(:android)
platform :android do
lane :build do |options|
if (ENV["ANDROID_KEYSTORE_PASSWORD"] && ENV["ANDROID_KEY_
PASSWORD"])
properties = {
"RELEASE_STORE_PASSWORD" => ENV["ANDROID_KEYSTORE_
PASSWORD"]
"RELEASE_KEY_PASSWORD" => ENV["ANDROID_KEY_PASSWORD"]
}
end
gradle(
task: "clean",
project_dir: project_dir,
properties: properties,
print_command: false
)
gradle(
task: "assemble",
build_type: "Release",
project_dir: project_dir,
properties: properties,
print_command: false
)
end
end
156
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
You can start with the match action. It helps in managing and distrib-
uting iOS certificates and provisioning profiles among your team mem-
bers. You can read about the idea behind match in the codesigning.
guide concept.
Simply put, match takes care of setting up your device in a way that it
can successfully build an application that will be validated and accepted
by the Apple servers.
Note: Before you move any further, make sure that your
init match for your project. It will generate the required
certificates and store them in a central repository where
your team and other automation tools can fetch them.
Another action that you could use apart from match is gym. Gym is sim-
ilar to the Gradle action in a way that it actually performs the build of
your application. To do so, it uses the previously fetched certificates
and signs settings from match.
157
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
default_platform(:ios)
before_all do
if is_ci? && FastlaneCore::Helper.mac?
setup_circle_ci
end
end
platform :ios do
lane :build do |options|
match(
type: options[:type],
readonly: true,
app_identifier: ios_app_id,
)
cocoapods(podfile: ios_directory)
gym(
configuration: "Release",
scheme: ios_app_scheme,
export_method: "ad-hoc",
workspace: ios_workspace_path,
output_directory: ios_output_dir,
clean: true,
xcargs: "-UseModernBuildSystem=NO"
)
end
end
You should be able to run lane build by running the same command
as for Android:
158
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
lane :deploy do
build
appcenter_upload(
api_token: ENV["APPCENTER_TOKEN"],
owner_name: "ORGANIZATION_OR_USER_NAME",
owner_type: "organization", # "user" | "organization"
app_name: "YOUR_APP_NAME",
file: "#{ios_output_dir}/YOUR_WORKSPACE.ipa",
notify_testers: true
)
end
That’s it! Now it is time to deploy the app by executing deploy lane from
your local machine.
159
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
version: 2.1
jobs:
deploy_ios:
macos:
xcode: 14.2.0
working_directory: ~/CI-CD
steps:
- checkout
- attach_workspace:
at: ~/CI-CD
- run: npm install
- run: bundle install
- run: cd ios && bundle exec fastlane deploy
workflows:
deploy:
jobs:
- deploy_ios
160
Don’t be afraid to ship fast with Continuous Deployment The Ultimate Guide to React Native Optimization
Pipeline for Android will look quite similar. The main difference would
be the executor. Instead of a macOS one, a docker react-native-android
Docker image should be used.
161
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 4
Ship OTA
(Over-The-Air) when
in an emergency
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
163
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
Even though the review times have gotten much better over the years,
it is still a good escape hatch to be able to immediately recover from
an error that slipped through the testing pipeline and into production.
There are two popular ways to implement OTA into your app. The first
and most popular tool for OTA updates is CodePush, a service that is
a part of Microsoft’s App Center suite. The second tool is created by
the Expo team and it’s called EAS Update.
App Center/CodePush
Configuring the native side
To integrate CodePush into your application, please follow the required
steps for iOS and Android, respectively. We decided to link to the official
guides instead of including the steps here as they include additional na-
tive code to apply and that is very likely to change in the coming months.
164
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
That’s it! If you have performed all the changes on the native side, your
application is now OTA-ready.
For more advanced use cases, you can also change the default settings
on when to check for updates and when to download and apply them.
For example, you can force CodePush to check for updates every time
the app is brought back to the foreground and install updates on the
next resume.
165
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
appcenter login
And then, a release command to bundle React Native assets and files and
send them to the cloud:
Once these steps are complete, all users running your app will receive
the update using the experience you configured in the previous section.
EAS Update
EAS stands for Expo App Services. One of its tools is EAS Update. It’s
a nice alternative to CodePush, especially if you’re already running an
Expo app. As with other Expo products, it provides superior DX and is
usually a pleasure to work with.
166
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
Info: To get EAS Update working in your project with the bare React
Native workflow, you need to set up Expo in your project. See the guide
to make that work correctly.
eas update:configure
And after this command, you need to set up the configuration file for
builds:
eas build:configure
After running these commands, the eas.json file will be created in the
root directory of your project. It contains the whole config for EAS to
run properly.
167
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
{
cli: {
version: '>= 2.8.0',
},
build: {
development: {
distribution: 'internal',
android: {
gradleCommand: ':app:assembleDebug',
},
ios: {
buildConfiguration: 'Debug',
},
},
preview: {
distribution: 'internal',
},
production: {},
},
submit: {
production: {},
},
}
Info: If you’re using the bare React Native workflow, you will need to
make some additional configuration. Read the guide to see how to do it.
168
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
Creating update
Once you have installed this build on your device, we’re ready to send
the update to the devices! So make some changes to the JS part of the
app to see if it actually works after we install the update on the phone,
then you’re ready to run the command which created the update, and
upload it to EAS.
Once these steps are complete, all users running your app will receive
the update with your changes!
You can configure EAS Update in many different ways - you can play
with different branches, different setups, and even a custom host up-
date service.
EAS Update is very handy when you need to deploy your changes to
the production, but it’s also very handy in the development process.
It’s convenient to allow other developers to preview the update for re-
viewing purposes.
For example, it may happen that your backend will stop working and
it causes a crash at startup. It may be a mishandled error - you never
had a backend failure during the development and forgot to handle
such edge cases.
169
Ship OTA (Over-The-Air) when in an emergency The Ultimate Guide to React Native Optimization
You can fix the problem by displaying a fallback message and informing
users about the problem. While the development will take you around
one hour, the actual update and review process can take hours if not
days.
With OTA updates set up, you can react to this in minutes without
risking the bad UX that will affect the majority of users.
170
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 5
This all leads us to the fact that developing an app is a process. There
are some interactions that lead to results. And, what is most important,
the processes can be optimized.
172
Make your app consistently fast The Ultimate Guide to React Native Optimization
One of the most effective ways of doing that is using the DMAIC meth-
odology. It’s very data-driven and well-structured and can be used to
improve React Native apps. The acronym stands for Define, Measure,
Analyze, Improve, and Control. Let’s see how we can apply each phase
in our apps.
Define
In this phase, we should focus on defining the problem, what we want
to achieve, opportunities for improvement, etc. It’s important to listen
to the customer’s voice in this phase - their expectations and feedback.
It helps to better understand the needs and preferences and what prob-
lems they are facing. Next, it is very important to measure it somehow.
Let’s say the customer wants a fast checkout. After analyzing the
components, we know that to achieve this we need a swift checkout
process, a short wait time, and smooth animations and transitions. All
of these points can be decomposed into CTQ (Critical-to-Quality) that
are measurable and can be tracked. For example, a short wait time
can be decomposed into a quick server response and a low number
of server errors.
Another handy tool is analyzing common user paths. With good track-
ing, we can analyze and understand what parts of the app are mostly
used by the users.
173
Make your app consistently fast The Ultimate Guide to React Native Optimization
Measure
Since we already know where we want to go, it’s time to assess the
starting point. It’s all about collecting as much data as possible to get
the actual picture of the problem. We need to ensure the measurement
process is precise. It’s really helpful to create a data collection plan and
engage the development team to build the metrics. After that, it’s time
to do some profiling.
One of the most popular tools is React Profiler, which allows us to wrap
a component to measure the render time and the number of renders.
It’s very helpful because many performance issues come from unnec-
essary rerenders. Discover how to use it here:
{
id: "Component",
phase: "mount",
actualDuration: 1.3352311453,
baseDuration: 0.95232323318,
...
}
174
Make your app consistently fast The Ultimate Guide to React Native Optimization
https://shopify.github.io/react-native-performance/docs/guides/flipper-react-native-performance
175
Make your app consistently fast The Ultimate Guide to React Native Optimization
On the native side, the most common method is using Native IDEs -
Xcode and Android Studio. There are plenty of useful insights which
can be analyzed and lead to some conclusions and results.
Analyze
The goal of this phase is to find the root cause of our problem. It’s a
good idea to start with a list of things that could potentially cause the
problem. A little brainstorming with a team is really helpful here.
Finally, it’s time to test the hypothesis. For example, if the main problem
is low FPS, and the potential major cause is related to list rendering,
we can think of some improvements in the area of images in the list
items. We need to design a test that will help us accept or reject the
hypothesis - it will probably be some kind of proof of concept. Next,
we interpret the results and decide if it was improved or not. Then we
make a final decision.
176
Make your app consistently fast The Ultimate Guide to React Native Optimization
Improve
Now we know what our goal is and how we want to achieve it, it’s time
to make some improvements. This is the phase where optimization
techniques start to make sense.
Before starting, it’s a good idea to have the next brainstorming session
and identify potential solutions. Depending on the root cause, there
might be a lot of them. Based on the last example with images on the
list item, we can think about implementing proper image caching and
reducing unnecessary renders.
After outlining the solutions, it’s time to pick the best one. Sometimes
the solution that gives the best effects might be extremely costly, e.g.
when it’s necessary to make some architectural changes.
It’s then time to implement the solution. After that, it’s required to prop-
erly test it and we are done!
177
Make your app consistently fast The Ultimate Guide to React Native Optimization
Control
The last step is the control phase. We need to make sure that every-
thing works well now. The performance will degrade if it is not under
control. People tend to blame devices, the used technology, or even
users when it comes to bad performance. So what do we need to do
to keep our performance on a high level?
We need to make sure that we have a control plan. We can use some
of our work from the previous phases to make it. We should point out
focal points, some measurement characteristics, acceptable ranges
for indicators, and testing frequency. Additionally, it is a good practice
to write down some procedures and what to do if we spot issues.
It's a great addition to any app builders that want to have insights on
how the performance is distributed across all the devices that install
their apps. Based on real user data.
178
Make your app consistently fast The Ultimate Guide to React Native Optimization
179
Make your app consistently fast The Ultimate Guide to React Native Optimization
The simplest test you can write would look something like this:
Code: Component.perf-test.tsx
This test will measure the render times of Component during mount-
ing and the resulting sync effects. Let’s take a look at a more complex
example though. Here we have a component that has a counter and a
slow list component:
return (
<View>
<Pressable accessibilityRole="button"
onPress={handlePress}>
<Text>Action</Text>
</Pressable>
<Text>Count: {count}</Text>
180
Make your app consistently fast The Ultimate Guide to React Native Optimization
fireEvent.press(button);
await screen.findByText('Count: 1');
fireEvent.press(button);
await screen.findByText('Count: 2');
fireEvent.press(button);
fireEvent.press(button);
fireEvent.press(button);
await screen.findByText('Count: 5');
};
When run through its CLI, Reassure will generate a performance com-
parison report. It’s important to note that to get a diff of measurements,
we need to run it twice. The first time with a --baseline flag, which
collects the measurements under the .reassure/ directory.
181
Make your app consistently fast The Ultimate Guide to React Native Optimization
After running this command, we can start optimizing our code and see
how it affects the performance of our component. Normally, we would
keep the baseline measurement and wait for performance regressions
to be caught and reported by Reassure. In this case, we’ll skip that
step and jump straight into optimizing, because we just noticed a nice
possibility to do so. And since we have our baseline measurement for
reference, we can actually verify our assumptions and whether the im-
provement was real or only subjective.
Once we’re done, we can run Reassure a second time. Now without the
--baseline flag.
182
Make your app consistently fast The Ultimate Guide to React Native Optimization
Now that Reassure has two test runs to compare—the current and the
baseline—it can prepare a performance comparison report. As you can
notice, thanks to applying memoization to the SlowList component
rendered by AsyncComponent, the render duration went from 78.4 ms
to 26.3 ms, which is roughly a 66% performance improvement.
When connected with Danger JS, Reassure can output this report in the
form of a GitHub comment, which helps catch the regressions during
code review.
183
Make your app consistently fast The Ultimate Guide to React Native Optimization
You can discover more use cases and examples in the docs.
Michał Chudziak
Independent Consultant @michalchudziak.dev
184
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 6
Know how to
profile iOS
Know how to
profile iOS The Ultimate Guide to React Native Optimization
Xcode provides some basic tools to do the first report. You can monitor
the CPU, Memory, and Network.
186
Know how to
profile iOS The Ultimate Guide to React Native Optimization
It also provides an extra monitor that isn’t shown by default but can
help you inspect your UI - it's the View Hierarchy.
When the app is running and you are on the screen you want to inspect,
click on Debug View Hierarchy.
This will show your current UI in a 2D/3D model and the view tree.
This will help you to detect overlappings (you can’t see a component)
or if you want to flatten your component tree. Even though RN does a
view flattening it sometimes can’t do it with all of them, so here we can
do some optimization focusing on specific items.
187
Know how to
profile iOS The Ultimate Guide to React Native Optimization
Let’s say we have a TODO list app, and when the Add button is pressed,
it adds the new item to the list. However, it takes a couple of seconds
to show up on the list because there is some logic before the item is
added. Let’s go to our dev toolbox and pick up our first instrument so
we can confirm and measure the delay.
iOS Instruments
Instruments is a debugging and profiling tool that comes prepackaged
with xCode, and is literally a box of tools, each of them serving a differ-
ent purpose. You choose from a list of templates, and you choose any
of them depending on your goal: improving performance or battery life
or fixing a memory problem.
We are going to use Time Profiler. Let’s dive into it. With xCode open,
we go to Open Developer Tool -> Instruments. Then, scroll down to find
the Time Profiler tool.
188
Know how to
profile iOS The Ultimate Guide to React Native Optimization
It will open a new window. To start profiling your app, click on the drop-
down menu and select your device and the app.
When the app opens, start using it normally, or in this case, add a new
TODO item.
After playing around and adding the new TODO item, we can see there
is a big blue rectangle, which means there is something that is taking
a lot of time to finish. Let’s take a look at the threads.
189
Know how to
profile iOS The Ultimate Guide to React Native Optimization
You can expand by pressing option+click over the chevron, which will
expand to display useful information. At least for now it is showing the
memory address, but we will need to find another way to find where
the problem is.
We click Start so the profiler begins. We do the same flow and actions
as before when profiling with Time Profiler. When we Stop, we will see
all the data collected.
190
Know how to
profile iOS The Ultimate Guide to React Native Optimization
By default the data will be sorted bottom-up with the heavy tasks at
the top. We can see that a function called doFib is taking ~14 sec to
complete, it is a good start, let’s go into that function and see what we
can do. The fixes will vary depending on your code.
As we can see, the fix we applied did work, we aren’t seeing the big
blue rectangle like before. This is a good sign. Let’s continue with our
profiling path to check how it looks in Flipper.
Start profiling the app one more time using Hermes Debugger (RN) ->
Profiler.
We don’t see the doFib function anymore, only other expected RN tasks.
191
Know how to
profile iOS The Ultimate Guide to React Native Optimization
70% of the users will leave the app if the response to a given action
takes too long. Profiling our apps has become one of the main steps
in our development life cycle. Using specific tools like Time Profiler will
help us understand if our app is responding fast or where we could find
areas of improvement. Remember, users are becoming more sensitive
to speed and delays, even a 0.1 sec of improvement can increase a
conversion rate by 10.1%.
192
The Ultimate Guide to React Native Optimization
PA RT 3 | C H A P T E R 7
Know how to
profile Android
Know how to
profile Android The Ultimate Guide to React Native Optimization
194
Know how to
profile Android The Ultimate Guide to React Native Optimization
If you have not installed Android Studio yet, you can install it using this
link.
To open the Profiler, choose View > Tool Windows > Profiler from the
Android Studio menu bar:
195
Know how to
profile Android The Ultimate Guide to React Native Optimization
After that, go to the Profiler tab and add a new profiler session:
Wait for the session to attach to your app and start performing actions
that could cause some performance issues, like swiping, scrolling, nav-
igating, etc. Once you’re done, you should see some metrics like these:
196
Know how to
profile Android The Ultimate Guide to React Native Optimization
Each greenfield React Native app has only one Android Activity. If your
app has more than one, it’s most likely a brownfield one. Read more
about the brownfield approach here. In the above example, we don’t
see anything interesting. Everything works fine without any glitches.
Let’s check each metric:
• The CPU metric is strictly correlated to energy consumption because
the CPU needs more energy to do some computations.
• The memory metric is not changing while using the app, which is
expected. Memory usage can grow, e.g. when opening new screens,
and drop when the garbage collector (GC) releases free memory,
e.g. when navigating out of a screen. When memory increases un-
expectedly and keeps on growing, it may indicate a memory leak,
which we want to avoid, as it can crash the app with out of memory
(OOM) errors.
• The network section has been moved to a separate tool called the
Network Tab. In most cases, this metric is not needed, because it
is mostly related to the backend infrastructure. If you would like to
profile a network connection, you can find more information here.
• The energy section gives hints on when our app’s energy usage
is low, medium, or high, impacting the daily experience of using
the app.
197
Know how to
profile Android The Ultimate Guide to React Native Optimization
198
Know how to
profile Android The Ultimate Guide to React Native Optimization
Let’s imagine you would like to pick the best list or scroll view compo-
nent for your React Native app, which has the best performance on a
lower-end device. You noticed the current solutions could be revamped
or improved and you started working on this. In your experiment, you
would like to check how your solution works for LE devices using the
above-described solution. When you double-clicked on CPU, you could
spot the below data:
Here you can see the mqt_js thread is used almost all the time and does
some heavy computation because your computations are done on the JS
side. You can start thinking about how to improve it. There are multiple
options to check:
• Replace the bridge with JSI in terms of communication – do tests if
JSI is faster than the bridge.
• Move some part of the code to the native side – on the native side
you have more control over threads execution and can schedule some
work to not block the JS or UI thread.
• Use a different native component – replace the native scroll view with
your custom solution.
• Use shadow nodes – do some expensive calculation with C++ and
pass it to the native side.
You can try out all of those solutions and compare the effect between
each other. The profiler will provide you with a metric and based on that
you can make a decision about which approach fits best to your partic-
ular problem.
199
Know how to
profile Android The Ultimate Guide to React Native Optimization
System Tracing
Using the Android Studio CPU Profiler, we can also make a system
tracing. We can check when the appropriate function has been called.
We can triage all threads and see which function is the costliest which
affects the UX. To enable system tracing, click on the CPU section and
select System Trace Recording
200
Know how to
profile Android The Ultimate Guide to React Native Optimization
After some interaction, you should be able to see all the threads with
details:
You can also save your data by clicking the Save Button:
201
Know how to
profile Android The Ultimate Guide to React Native Optimization
You’ll also want to check the official Android Profiling guide by the Re-
act Native core team. They use different tools, but the outcome will be
the same. The guide provides case studies and how to spot an issue
on different threads:
• UI thread
• JS thread
• Native module thread
• Render Thread (only Android)
You can find more about threading models in the New Architecture
chapter.
202
Know how to
profile Android The Ultimate Guide to React Native Optimization
You can also automate your experiments with e2e tests and generate
reports locally or on a CI. Those reports can be used to compare solu-
tions with each other.
203
The Ultimate Guide to React Native Optimization
Thank you
We hope that you found the aforementioned best practices for React
Native optimization useful and that they will make your work easier.
We did our best to make this guide comprehensive and describe both
the technical and business aspects of the optimization process.
If you enjoyed it, don’t hesitate to share it with your friends who also
use React Native in their projects.
Authors
Michał Pierzchała
As Head of Technology at Callstack, he is passionate about building
mobile and web experiences, high-quality JS tooling, and Open Source.
Core Jest and React Native community contributor. Space exploration
enthusiast.
twitter.com/thymikee
github.com/thymikee
Jakub Bujko
With multiple years of delving deep into react.js development in his
pocket, Kuba went on to master mobile development. Passionate about
edge technologies, clean and minimalistic code, and charting the paths
for the future of React and React Native development.
twitter.com/f3ng__liu
github.com/Xiltyn
Maciej Jastrzębski
React & React Native developer with multiple years of experience build-
ing native iOS and Android apps. Passionate about building robust and
delightful apps along with writing well-architected and readable code.
Loves learning new things. He likes to travel in his free time, hike in the
mountains, and take photographs.
twitter.com/mdj_dev
github.com/mdjastrzebski
Piotr Trocki
Software developer who started his journey from mobile apps. Now
Piotr is focused on mastering both Native (Android, iOS) and React
Native technologies in brownfield applications. When not coding, he
205
Thank you The Ultimate Guide to React Native Optimization
Jakub Binda
A dedicated software developer who pays a lot of attention to the de-
tails in every task he does. Always committed and eager to learn, Kuba
likes to create things and dive into how they work. A father of two and
a husband to the woman of his life. Those two roles motivate him the
most and give him the strength to move mountains.
twitter.com/jbinda
github.com/jbinda
Szymon Rybczak
Szymon is a 16-year-old React Native Developer with two years of ex-
perience and currently doing mobile app development at Callstack. In
his free time, he likes to discover new and interesting technologies.
github.com/szymonrybczak
twitter.com/SzymonRybczak
Hur Ali
TypeScript enthusiast mastering the React-Native and Native realm.
He feels best in diving deep with mobile tech, making proof-of-concept
projects, and experimenting with new technologies. In his free time, he
enjoys playing FIFA and contribution to OSS.
twitter.com/hurali97
github.com/hurali97
Oskar Kwaśniewski
React Native Developer at Callstack. Currently, he’s strengthening his
knowledge of native development and making some OSS contributions.
206
Thank you The Ultimate Guide to React Native Optimization
During his free time, he enjoys riding a bike, going to the gym, and play-
ing video games.
github.com/okwasniewski
twitter.com/o_kwasniewski
Tomasz Misiukiewicz
React Native Developer at Callstack with a strong background in web
development. Big fan of keeping the code clean and simple. Loves to
learn new stuff and enhance his programming skillset every day.
github.com/TMisiukiewicz
twitter.com/TMisiukiewicz
Eduardo Graciano
Senior mobile developer at Callstack. Hacking almost all kinds of mobile
tech and always looking forward to making readable and maintainable
code without messing up everything.
github.com/gedu
twitter.com/teddydroid07
Andrew Andilevko
React Native developer with a background in Android development. He
likes complex tasks to constantly expand his expertise and knowledge.
He spends his free time with his wife and pug.
github.com/andrewworld
207
The Ultimate Guide to React Native Optimization
About Callstack
We are a team of React and React Native experts and consultants
focused on delivering cutting-edge technology. We love to share our
knowledge and expertise with others.