Building apps with React Native at ticketea. Lessons Learned

"Ok, let's make an app. I mean... two apps?"

The decision

In spring 2016 we decided to make a new mobile app for our Android and iOS users.
One of the premises was to create a project everybody could join in. Even those with no previous experience in mobile development. We had a bunch of front-end web developers as well as a few members with dedicated iOS and Android experience. We considered that the full-native app would not scale for us. Instead, we aimed to a learn once, code once, run everywhere solution. Why making two separate apps in their respective languages and toolchains when you can make a single app with web tools?

We analysed different frameworks for hybrid apps, like Xamarin, Titanium, Supersonic and the like. And we thought that React Native (RN) was the way to go. Facebook support, a known framework (React), a very popular architecture (Redux), web standards, the possibility of adding pure real native code besides JavaScript, and you know, it was very 2016 at that time.

And off we went.

After a few weeks, there was an actual app you could toy with, keeping both Android and iOS own native implementations to a bare minimum. It looked like the way to go. It was seriously powerful. But it had a non-trivial amount of drawbacks:

  • Facebook was pushing the project very hard and with no mercy. Every 15 days there was a new release with brand new breaking changes. It might sound good for newcomers, but any project with this level of demanding updates becomes daunting, confusing and ultimately, hard to maintain in acceptable conditions.
  • Besides, migrations to newer versions usually led to traumatic developer experiences. We were spammed with red shinny error screens for days and with the well-known "I don't know what I'm doing" feeling.
  • The community is great. Everybody is trying to make their best at all times in order to push the React boundaries. The downside is: you depend on too many third libraries just for the basic stuff (geolocation, inexistent views or controllers, etc) you'd find in iOS and Android frameworks from the get-go.

Finally, the app was published on both platforms in May 2016. After this initial release, we pushed a few more updates using CodePush. The application was based on ReactNative 0.23, the latest version available at the time.

In autumn, we picked up the application development again. In the meantime, ReactNative had reached version 0.41. iOS and Android operating systems had new versions released as well. These two factors made clear that the application was in need of a refactor, to bring it up to date with the newest developments of the React Native ecosystem, which in the meantime had changed. A lot.

Then, another developer happily took over the project, but he couldn't imagine the storm that was coming.

Lessons learned the hard way.

The language

First things first: everybody claims to know JavaScript. Unfortunately, not as many do actually know it well.
It is dynamic, loosely typed and very prone to silly mistakes. ECMAScript 6 improvements makes it much more readable, concise and less ugly, but still, the problem remains: it's too easy to make mistakes and it's very hard to track them.

For some, TypeScript would be a perfect solution. That is optional. What is paramount is using a linter. Use a linter for ES6 and ReactNative from the very beginning. At ticketea, we use the Airbnb presets with a few changes of our own. It might sound obvious to many, but this tool was not there from the beginning. And it helped a lot. A linter will keep your codebase coherent, free of silly mistakes, pretty and what's more important: it will teach you good practices as you work along with.

Also, you could use Flow from Facebook, which will you spot incorrect types as well. We didn't use that extensively at the beginning so later on it was just too hard to keep with it.

You won't be able to skip Javascript this time, so the best thing you can do is to work with it with lesser suffering.

The frameworks

React Native. This is why you are here in the first place, right? I used to think of it as a self-contained package with everything that I needed in order to make a mobile app with shared components. And I was told that any web engineer with React knowledge could be capable of making a mobile app with this. It was a big serious lie.

Think React Native more as a powerful rendering tool that allows you to communicate screens (called 'scenes') using abstract and reusable components for both Android and iOS. Because it's very dependent (and glued) to the actual native project, it provides a handy Command Line Interpreter for running, compiling, etc. But make no mistake: you need to know how to maintain an Android and a iOS project. If you don't, you'll have to learn.

It will be the most important and resilient dependency in your project. It's almost everywhere. And it's a framework, so it means you don't have power over it. Yes, it is RN who calls you through a lifecycle and continuous updates, like UIKit and Android Framework would. But that doesn't mean you must throw everything inside React components. Quite the opposite. Keep your React Components free of responsibilities as much as you can. The dumber they are, the better. We'll get back to it later on.

This was another pitfall for us. Too many things were glued to how ReactNative works. If RN changes, you might have to change things too. And that's not good.

Redux has almost nothing to do with React Native. Redux, React and React Native are separate libraries. The latter cannot work without React. But Redux is just another tool that happens to be very optimal for React Native. With this in mind, Redux and React Native files should not be visible from each other. Keep both agnostic and you will be happy.

We won't talk about what Redux is for the sake of brevity.

At the very beginning, our React Native components knew a bit about how Redux worked. That caused a Domino effect when changing Redux implementation because UI logic was somehow hooked to business logic.

React and React Native equals to UI and OS communication. Redux means storage, global state and business logic affecting that state. React components change upon state alterations. So you need to connect both worlds somewhere. We learned that this communication layer had to be dumb, thin, simple and outside React files. Bear in mind that when connecting both, we never import React libraries.

Our conclusions

For what we just said, it might look like building a ReactNative app is a wrong idea. It's not. It could be the right choice. It is just not a happy idea and should be weighted up among team members, not chosen because everybody else is using it. You'll face a wide variety of issues, which we solved and we are sharing with you now.

  • Mobile development knowledge on iOS (besides Xcode and the Apple developer portal, etc) and Android (Android Studio, Google developer platform, emulators, Gradle, etc). As previously stated, if you don't have this basic knowledge, take your time to learn and add it to your estimations.
  • Continuous maintenance every month. React Native release pace slowed down and it's currently just one release per month. They keep adding new features, fixing things and breaking others. Unfortunately, a RN project should never be abandoned and your team will need to catch up with Facebook, upgrade and maintain the codebase every once in a while (not every 6 months). Otherwise, you might have to start over.
  • Avoid third libraries/dependencies as much as possible. If you do need them, make sure they are easily replaceable. If they begin to alter your code and business logic, make a wrapper. It's not hard to find that an important library in your project has been left out by its creator. There are RN missing components that are eventually added to the core library. Others are just commodities that save you tones of time. Pick your battles wisely, and don't expect to win and forget. RN is very organic and it's on the move.
  • Use Flexbox. Don't fight the framework. React Native heavily relies on flex for layout. Use separate Stylesheet objects (when it makes sense) and never hardcode inline style properties. Bear in mind that it's not actual Flexbox (CSS) but a copycat implementation for native components (don't expect it to work exactly as original from the web).
  • Use yarn. It's what npm should be. Why yarn is not just the latest npm release, only the Gods and the JS community can answer. Code examples and tutorials use npm, but you don't have to. They are fully interoperable through the same package.json.
  • Separate Redux from React Native if you are going to use both. Retrieve state and actions from redux files, and pass them using a connector which will map them as props and functions.

With great power comes great responsibility

We'll talk about our specific approach to a React Native app in another post. We'll explain our decisions, what problems they solve and what downsides they ended up creating.

Let us know your opinions about your own React Native experience or what do you think about ours!