Lessons learned while integrating React Native into FidMe

Estimating the changes to come

When we started working on adding React to our existing native app, we already had a first experience on the matter.

Our first try went pretty well. It was all about adding a completely separated part to an existing app.

Almost no communication between native and JS was necessary. This very simple flow made it extremely easy for us to plug React Native as a side module.

On FidMe, the idea was to add several React bricks among the whole app. Some views would be integrated among existing native views/fragments (the “Marques” tab on the left below), others would just popin seamlessly during existing native workflows (ticket scanning).

One of our React Native flows in FidMe

In theory this would allow us to iterate much more rapidly on new views as they would be shared between Android and iOS.

Unfortunately we underestimated the number of bridges necessary to communicate properly between native and react.

Communicating between native and React

There are many ways to communicate information among your app. But when it comes to passing information back and forth between native and React, you do not have so many options, so be careful when choosing.

We narrowed down the options to the three following :

Choice n°1 : Passing information on startup

That is the way we chose for the majority of our API calls. It is very convenient, you only need to instantiate your react part with default props :

let mockData:NSDictionary = ["token": HTTPCaller.accessToken as AnyObject, "serverURL": Constants.Network.serverURL]
let rootView = RCTRootView(
    bundleURL: jsCodeLocation,
    moduleName: "YourReactModule",
    initialProperties: mockData as [NSObject : AnyObject],
    launchOptions: nil

Do the equivalent in your Android app, then you can just access this information as props on your root component :

const { token, serverURL } = this.props; // We store these as properties of our API classes

We think that this is the way to go if your use case can adapt to it. It allows you to just fetch() data normally from your API, while automatically adapting to server Url, language, etc.

A perfect use case would be developing a completely separated part of your app with React, meanwhile calling the same server as the rest of your app, but different API endpoints.

It consumes very little time, gives you a lot of flexibility in JS and is probably the less hard to maintain. But few use cases can rely only on it.

Choice n°2 : Re-developing an entire API call process for JS parts

That way is probably the less efficient in terms of code duplication.

But still, no bridge will be necessary, meaning your JS developer will never need to ask anything to the Android/iOS developer.

One potentially huge drawback is that you will have to maintain the same API call in 3 different languages.

A good use case I can think of is if you want to replace step by step your native views with React ones.

Choice n°3 : Create bridges for all your API calls

In some use cases, you may need to keep the webservice logic in native only. Many reasons may force you to do so :

  • Lots of things to bridge in order to achieve a single API call in JS
  • Use of old or in-house communication protocols
  • Too strong dependency on the webservice in native parts
  • Offline mode, callbacks, cache you may have added to your native calls (this was the case in our project)
  • Architecture that prevents you from exposing sensitive information (token, auth infos) publicly in your classes (this was also our case)

When you encouter one of these cases you might want to choose this solution.

You will not bridge information, you will bridge your entire API calls suite.

You will end up with classes with lots of bridged methods like this one (android example).

Notice the @ReactMethod annotation that allows to expose this method to React’s NativeModules.

This method allows us to update user information in JS like this :

CustomerAPI.updateInfo({ firstname, lastname }).then(doSomething);

It ends up being very clean on the JS side.

The best way to make this work properly is to ensure that Android and iOS developers have very consistent and similar ways of calling your API.

Whether you choose the first, second or third option, you need to very carefully think about it.

Our mistake was to underestimate the importance of consistency between calls. In short :

  • As a JS developer, always involve both Android and iOS developers when choosing a solution
  • Keep it consistent among your entire app
  • Think about maintainability and scalability of your bridges as they may be hard to test, hard to update, and yet crucial to your app

Back and forth navigation between native and React

Back and forth may be hard to deal with

It think that this is a potential major source of time loss. In FidMe we had several workflows that required to switch between existing native views and react views.

In our mind that would not be a problem, we would just pop a native screen, then dismiss it, then pop a React view.

But it has not been that simple.

When poping in a native screen, you interfere with the current react navigation stack. You add a native screen on top of it, and in our case this screen was not pushing an entry in our navigation stack.

In the end, we chose to use React Navigation and redesigned the header to match the native ones. It works ok, it is easy to customize, plus documentation is good, performance is correct and native feeling is ok.

But our solution to manage switch between React and Native is not perfect. Everytime we switch from native to react, we re-instantiate an entire React flow, with its custom entry point. It works fine, but we encounter something like 100ms delay when switching. It may be a problem in the end.

Spend time working on your project configuration

Misconfigurations of your React integration may cause issues such as : hot reload not working, conflicts with native libraries, dev menu not working, JS debugger not working, slow startup etc, etc.

We think that spending time creating a clean configuration of your project is one the keys to being satisfied with React Native.

Just to make a quick fix, you may be tempted to leave that non working debugger aside. But unless you really don’t have time for that, do not let these small issues ruin your productivity.

Furthemore, your native developers who continue to work on the initial project may not want to be bothered by the React integration. Try to simplify the build process for them, write documentation on how to install the app with React, show them, and explain to them how the whole thing work, otherwise you may end up upsetting them.

Do not expect to work twice as fast as an average

The process of creating views with React Native has always been something kind of magic. People often demonstrate React Native power just by showing off “Hot reload” features, or pixel perfect compatibility between Android and iOS.

When considering only these exemples, you may end up thinking “Ok, we will be twice as productive as when writing native apps ! That’s great !”. Yeah, but, not always…

When developing simple, “Javascript only” apps, you may reach a very high productivity, sometimes even higher than twice as fast compared to native. Views build themselves just as you type, it really is amazing.

But in a real life app, and if your product needs very specific things, you will eventually end up making native code, bridging things. The time you gained with hot reload may be compensated by the time you will spend resolving conflicts between libraries, building native code, bridging views, etc.

It is probably an illustration of React Native’s immaturity but because of its rapid changes, some third party libraries may not always work, may have compatibility issues, and fixing them up, or adapting them for your need is very time consuming.

On an average Javascript only app, we estimated an overall 1.6x faster than developing for both iOS and Android. On FidMe, because we had a lot of bridges and custom flows, we estimated that we would have needed roughly the same time.

But one thing to consider, is that updates, new features, etc, won’t require more time to make bridges and we are hoping to reach the desired 1.5x faster goal sooner or later.

In the end, we are very satisfied by our React Native integration.

Not everyone at the same scale of course. But considering the fact that at Snapp’ we are surrounded by native developers, we basically have no limit to what React can do for us.

Anything can be bridged, any performance demanding thing can be done natively and mixing native and JS is easily achievable.

If you feel confident writing JS and you have native developers by your side, React Native cannot be a bad choice.