FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade
Welcome to FullStack Labs. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide many other benefits of the site. For more information, please see our Cookies Policy and Privacy Policy.

Tips and Tricks for a Production-Ready App using React Native

Written by 
Miguel Espinoza
,
Senior Software Engineer
Tips and Tricks for a Production-Ready App using React Native
blog post background
Recent Posts
Getting Started with Single Sign-On
Choosing the Right State Management Tool for Your React Apps
Accessibility in Focus: The Intersection of Screen Readers, Keyboards, and QA Testing

There’s an art to delivering production-ready applications using React Native. After all, mobile users are accustomed to high-quality apps and have great expectations. To help you, here are a few tips and tricks to enhance their experience:

Table of contents

  1. Provide feedback throughout your users’ journey
  2. Network conditions
  3. Responsive UI layout rendering
  4. Edge case scenarios using monkey testing
  5. Error and exception handling

Provide feedback throughout your users’ journey

The “happy” path is a well-known concept in software development where no exceptions or errors occur. As developers, it’s our duty to make sure we design solutions that keep users on the happy path. If they end up on “unhappy” paths, well, we end up with unhappy users.

Designing solutions that abstract UI states can help create deterministic app flows and more easily keep users within deterministic “happy paths”

Changes in UI state happen regularly, especially with network requests.

Usually, we see this pattern:

	
requestState = {
   data,
   isLoading,
   error,  
}
	

Tip: you can improve this by moving the request state to a deterministic set of strings:

	
requestState = {
   data,
   state: 'LOADING' | 'COMPLETE' | 'ERROR'
}
	

This may seem like a small change, but we’ve removed the complexity involved in handling the loading and error states separately.

Here’s an example of how we can use requestState, for a download progress indicator:

	
const DownloadProgressIndicator = ({requestState}) => {
  switch (requestState.status) {
    case RequestStatus.LOADING:
      return (
        
      );
    case RequestStatus.ERROR:
      return (
         
      );
    case RequestStatus.COMPLETE:
      return (
         
      );
  }
};
	

By managing this state via a set of strings, we create a sequence of events with logic that can be managed and a simple experience for our users.

It’s important to note that requestState creates a foundation for providing feedback, as this will give users the necessary guidance for users to complete a task.

Network Conditions

Network conditions will vary; not all users will be connected to wi-fi, so we should test for those scenarios. 

Here are a few tips when the network is unstable:

Debugging using Network Link Condition

An easy way to test is using Xcode’s “Network Link Condition” feature.

Here are instructions on how to enable the feature for iOS physical devices.

With this enabled, you can discover issues related to loading states and race conditions that could impact the user experience.

An optimal configuration is setting 2G Network - average; this makes the app’s requests slow enough to notice odd behaviors while loading.

screenshot

Network Status Feedback

When a network connection is unstable or lost, it’s best to present the user with an alert so they understand immediately why the app is not behaving normally. You can accomplish this by using libraries like react-native-netinfo and listening to network changes.

Executing async fetch requests

Finally, fetch requests should be executed outside a component; this makes it easy to manage when unmounted. Executing asynchronous fetch requests when a component has unmounted may cause its state to change, triggering memory leaks. Ideally, fetch requests should be abstracted with libraries such as Redux or MobX. By doing this, you’ll be able to easily handle errors and store in a global state.

Responsive UI Layout Rendering

Next up, let’s look at the interface. Users expect well-functioning UIs – slow navigation or interactions can cause users to dislike your app. In Android, one major reason why apps may feel sluggish is due to overdraw.

What is overdraw?

According to the Google developer docs:

“Overdraw refers to the system’s drawing a pixel on the screen multiple times in a single frame of rendering. For example, if we have a bunch of stacked UI cards, each card hides a portion of the one below it.”

However, the system still needs to draw even the hidden portions of the cards in the stack. This is because stacked cards are rendered according to the painter’s algorithm: that is, in back-to-front order. This sequence of rendering allows the system to apply proper alpha blending to translucent objects such as shadows.

This can be enabled by going to Settings > Developer Options on your Android device and selecting Debug GPU overdraw from the HARDWARE ACCELERATED RENDERING section

screenshot

Here’s an example from the Google Developer docs. Tip: by reducing the amount of overdraw you are saving  GPU computation, making your app less resource-intensive and creating a snappy experience.

screenshot

It’s worth noting that these optimizations will also transfer over to iOS. Fortunately, when it comes to handling operations on the UI thread, React Native works very well out of the box.

Edge case scenarios using monkey testing

When developing apps, we can only account for so many variations of user interactions; when bugs are reported, we sometimes get surprised by how they were triggered. 

One way to catch these one-off interactions is to use monkey testing, which is a form of stress testing that adds confidence to the robustness of our apps. Android has a built-in solution for performing monkey tests with adb.

Enabling Screen Pinning

Tip: before running monkey tests on Android, enable Screen Pinning – this will prevent it from navigating outside the app.

This can be enabled by going to Settings > Security > Advanced > Screen Pinning and select “On.”

Running Monkey Test

Here’s an example running a monkey test on the Google Play Store app from an emulator using this command:

-- CODE language-bash keep-markup --
adb shell monkey -v 4000

You can customize input events by providing different arguments to the adb command. A comprehensive list of arguments can be found in Google’s UI/Application Exerciser Monkey documentation. 

The default arguments, however, capture a wide range of scenarios. This has helped me in the past to capture a few undefined object property errors.

Disabling Screen Pinning

When done running your monkey tests, you can disable Screen Pinning using this adb command:

	
adb shell am task lock stop `dumpsys activity | grep -A2 '(dumpsys
activity recents)'| grep '#'| cut -d ' ' " \
                "-f 7| cut -c 2-`
	

Monkey testing is a great way to discover bugs that are hard to reproduce. By identifying these one-off bugs you can increase the stability of your application.

Error and Exception Handling

It’s important when users end up in a situation where an error or an exception occurred that we give them feedback and next steps to remedy the situation. With React Native applications if you have unhandled JavaScript errors, you can end up with a white screen like this:

screenshot

Here are a few tips to display a better message:

JavaScript Errors

Using libraries such as react-native-error-boundary you can show a message to the user explaining that something went wrong. By doing so, you give users a quick solution to get back to their task, thus making the errors more forgiving.

Native Exceptions

Native exceptions in React Native give a much harsher experience than the JavaScript counterpart. These commonly occur from imported Native modules. If not handled, it will kill the app entirely and leave the user at the home screen. 

The library react-native-exception-handler is used to capture these exceptions and present a message that something went wrong and give users the ability to restart.

Unfortunately, iOS has a limitation with restarting apps; users must do it manually.

Lastly, it’s important to pair these solutions with reporting tools like Sentry to capture the exceptions and address them as hotfixes so they do not affect others.

Conclusion

Above are a few ways to make sure your app is ready for production and will deliver an exceptional user experience. Providing feedback throughout their journey when things go wrong is especially important so the user can continue on to accomplish their objective.

Miguel Espinoza
Written by
Miguel Espinoza
Miguel Espinoza

As a Software Engineer and Mobile Developer at FullStack Labs I'm primarily focused on building IOS and Android applications using React Native. Prior to FullStack Labs I was an Android Developer for ACS Technologies and a React Native Developer for PEER. I hold a Bachelor of Applied Science, Mathematics and Computer Science from Francis Marion University.

People having a meeting on a glass room.
Join Our Team
We are looking for developers committed to writing the best code and deploying flawless apps in a small team setting.
view careers
Desktop screens shown as slices from a top angle.
Case Studies
It's not only about results, it's also about how we helped our clients get there and achieve their goals.
view case studies
Phone with an app screen on it.
Our Playbook
Our step-by-step process for designing, developing, and maintaining exceptional custom software solutions.
VIEW OUR playbook
FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $75,000.