The Move to React Native
By: Matthew Eckstein
Tags: Software Engineering, React Native, Engineering Mangement, Mobile App Development

About six months after joining ClassPass, I found myself managing all of the mobile engineers in the department. This wasn’t by design, I just happened to take over all of the engineering squads that contained mobile engineers. It was an opportunity to examine and improve mobile engineering at ClassPass. One of the biggest opportunities we identified was exploring just how widely we could use React Native in our mobile development.

React Native is a tool; it’s right for some jobs and not for others. And like any tool, it has real power when applied to the right job. It can speed up development in a number of ways. You can surge web engineers into mobile projects in a way you can’t when writing native code, because it’s JavaScript and based on React. Its hot reloading increases engineering velocity since you don’t have to wait for compilation to see your changes. But the biggest benefit is that you can build features once and launch them on both iOS and Android apps.

Like many startups, ClassPass had more work on our product roadmap than we would ever have time to do. It was clear that building features for our mobile apps with half the effort could be a huge win for the company. But when you already have two large mobile codebases and mobile engineers who have had little to no experience with React Native, a transition like this can be complex and challenging. Success meant identifying those complexities, figuring out how best to meet those challenges, and remaining agile enough to keep iterating based on regular feedback. Our adoption of React Native was, for all of us at ClassPass, a real lesson in the huge benefits that can accrue from both careful planning and successful adaptation.

Buy-in From Key Engineers

Changes like this are usually a hard sell to your engineering team. Engineers who are proficient in Swift or Kotlin code don’t want to learn JavaScript or the React framework, particularly when being told to do so by a manager who’s never submitted code to a mobile codebase. It was clear I’d need allies in the engineering team. Two quickly came to mind. The first was a senior iOS engineer who had established himself at ClassPass through technical skill and an eagerness to share his knowledge (he was later promoted to be the mobile lead of ClassPass). The second was a recently-promoted Engineering Manager who was one of the first engineers to see the potential benefits of React Native. Both were much closer to the work, could lead by example, and were well respected by the other mobile engineers.

Find Executive Support

A large technological shift like this means you also need champions from above. Changes like this can have a huge beneficial impact on the business, but realizing them can take time. There’s the basic learning curve of the language and framework. Time spent hashing out best practices and optimizing operations are vital to the project’s success, but carry opportunity costs. Here’s where ClassPass’ culture of transparency and debate really paid off. We had to clearly articulate the long term business impact this kind of transition would have. Writing code once and launching on both mobile platforms simultaneously would be a huge benefit, especially as we were starting to discuss a major international expansion, where many markets have a much more even distribution of iOS and Android devices than ClassPass’ domestic customer base. Once everyone understood that, the executive and technical stakeholders were now aimed toward the same goal, vastly improving the project’s chance at success.

The Learning Curve

At the onset of this initiative, I thought it would help if my team could speak to some engineers who had been successful using it. Back in early 2017 that wasn’t easy to find. It had only been two years since Facebook open-sourced React Native. Artsy was one of the few companies that had experience actually using React Native and had spoken openly about it through multiple blog posts. The CTO was a friend, so I reached out and set up some time for our teams to speak. Hearing first hand about their experience gave us more confidence that this technology was viable for ClassPass.

The team started by using React Native for some smaller features, but we all agreed that some formal training would be valuable. One of our engineers knew of someone who’d led React Native training sessions, and so we scheduled a two-day class. The instructor introduced JavaScript, React, and eventually React Native. Our mobile engineers were very strong, senior, and already had some exposure to React Native; so within a few hours they pushed the instructor into material from the second day. This meant the second day could be used delving into advanced topics. It was great to see the instructor adapt so quickly into a bespoke curriculum based on my team’s desire to better understand advanced concepts like testing and best practices for a “brownfield implementation” like ours.

The Mandate

The team’s success with the training and these smaller projects convinced me we were on the right track but that I needed to keep pushing to make this happen. If each engineer had discretion to use React Native or write native code, many would choose native because it’s what they already knew. If that happened, we would have never realized the full benefits of React Native.

So the mandate I laid out was simple: Use React Native for all new projects on mobile and come speak to me when you think there should be an exception. The team’s reaction was mixed. Some engineers were excited about it and excited to learn something new, while others were skeptical. But the skepticism was kept to a manageable amount due to the mobile lead and the manager being on board, having gone through the training, and having completed some smaller features in React Native.

There was one clear exception to the React Native mandate from the beginning. Our Special Projects group were building a live video streaming product that used biometric feedback from body sensors via bluetooth. The work they were doing had deep integrations into the mobile operating systems, and was therefore not a good candidate for React Native. This exception was clear and non-controversial, and I was curious when we would find another clear exception. It took a full year.

Operations

At this point we had three repositories (iOS, Android, React Native). As we launched more of our features in React Native we faced more operational difficulties. For example, React Native code was pulled into the iOS and Android repos based on a pinned commit hash. This usually resulted in a broken build when an engineer updated the commit hash on either repo. And trying to proactively run UI tests for React Native code against both apps became a manual and time consuming process.

After much discussion in the mobile guild, it was decided the time had come for the “monorepo”. This would be a large project, but a necessary one if we were to get the most out of React Native. The guild lead broke it down into some releasable milestones and over a few months we had monorepo up and running.

In the new world of one mobile repository, changes to React Native code triggers a build to be run for both iOS and Android. Since UI tests are run against both apps, we ensure that master is always functioning for both iOS and Android. Code reviews are more productive since you can see what’s changing across platforms in one PR instead of three. Once we made this change, builds weren’t broken or full of terrible surprises. This was even more valuable for builds tied to our app releases. Reducing these issues helped our engineering velocity immensely. Engineers expect to catch their own bugs during a build. But when they find other people’s bugs, they have to reach out and both engineers are interrupted. Productivity takes a big hit and everyone gets frustrated. The new monorepo kept our mobile engineers much more productive and happy.

The Feedback Loop

Discussing and capturing feedback was a key part of our successful transition to React Native In addition to the large operational changes like the monorepo and the subsequent work on UI tests, many smaller decisions were made regularly. The mobile guild was the primary forum for these conversations. The mobile guild lead’s role in promoting and facilitating these discussions was critical. We also instituted longer “state of the union” meetings twice a year that were entirely focused around React Native. All of these mechanisms for capturing feedback would then feed into action items. Sometimes those actions were establishing and documenting best practices; at other times they were turned into projects on the mobile guild roadmap.

It’s always important to separate naysaying from valuable critical feedback. Interestingly, throughout our transition, the amount of criticism an engineer had about React Native was inversely proportional to how often they used it. The biggest naysayers used it the least, and tended to like it more as they used it more. Distinguishing naysaying from good feedback was also a product of knowing the engineers very well. I made it a priority to have regular one-on-one meetings, observe them directly in guild meetings, and talk about them with managers. With all of this input, it wasn’t too hard to distinguish legitimate critical feedback from simple personal preference.

Mobile Acquisition Flows and Concurrent Development

For years our app was only usable by subscribers. We’d acquire users on the web, who’d then use one of our apps to search for and reserve classes. We had recently “opened up” the iOS app and added a number of flows where you could sign up for a trial subscription upon opening the app or several other points in the process of searching for classes. This was a huge shift for ClassPass and our apps soon became a significant source of user acquisition. As we were about to start launching in markets with much more Android usage, it was critical that this work be ported over as soon as possible.

The mandate meant that all of this code was written in React Native, but the team that built it only had iOS engineers who were expected to launch it in the iOS app with an aggressive deadline. Because it was React Native we assumed it would be easy to port it to Android. This was a pivotal moment for mobile development at ClassPass because it showed great progress but also exposed some major issues. Android engineers were able to get the new flows into the app with roughly 25% of the effort (Two engineers in one month vs four engineers in two months). This was such a clear win for the business and for the team, that most of the skepticism around React Native evaporated. But it had also been a very frustrating month for those Android engineers. The frustration stemmed from our use of the “bridge” and the development of React Native code that was written only for iOS.

The bridge is the mechanism for passing data between the native world and the React Native world. Since both our mobile apps started as native apps, the bridge is used on all transitions from one view to another. But using the bridge causes latency. It also means that bridge modules have to be duplicated in each native codebase. In this case, Android engineers had to go into the native iOS code and try to figure out what the bridge module was doing and then attempt to replicate that in Java. One of the most important action items that emerged from this project was that we needed to adopt patterns that minimize the use of the bridge. It was also clear that it was much more efficient to work on iOS and Android in parallel, rather than developing for one platform and then porting that code over at a future date. Many React Native components have either iOS or Android specific configurations, and in this project engineers would run into components that were configured for iOS but not Android which slowed down development and was generally a frustrating experience.

Opening up our mobile apps for user acquisition was the last big project before we were ready to start developing features concurrently on both platforms. The experience and frustration our Android engineers had in porting over these user acquisition flows only reinforced that concurrent development was a move in the right direction.

The Next Exception

In early 2019 we completely rebuilt our search experience. Client-side development started in React Native as per the mandate. But early in this rewrite the mobile engineers saw latency in the search UX and started to doubt that React Native was going to work for this project. Tickets had been written and engineering work had already started, but now we were questioning what language to use. The product manager for search immediately flagged this a risk. A meeting was scheduled to discuss how to move forward. Because this topic touched both the search project and the mobile guild, most members of both groups came. It was a large meeting and I could tell a lot of people felt tense, like they were about to witness a React Native showdown.

The meeting started with an engineer on the search project describing the latency issue. Almost immediately the Engineering Manager who was the advocate for React Native (but was not involved in the search project) just stated that we clearly shouldn’t use it for this project. There was some silence and it seemed that people were surprised. They were expecting drama but they got quick agreement. Another senior engineer then said, “React Native is just a tradeoff between latency and development speed”. I thought that was perfectly articulated. I said as much and that I agreed with the decision not to use React Native for our search experience. I was thrilled that we finally found our second exception because it finally drew some sensible rules for when we use React Native. It all came back to the bridge.

Our search results page is very interactive. Most searches in large markets result in a seemingly infinite scroll of results. You can also swipe left and right to move to search results for different days, and there are other filter controls at the top of the page. All that interactivity requires moving data over the bridge and slows down the app. Most of the work we had done successfully in React Native were simple flows of one screen to another, or larger and more static pages like details pages for venues or classes. I had asked the team to use React Native unless they thought they had a good reason not to. We finally found another reason. Screens with a lot of interactivity should be done natively. ClassPass still defaults to React Native, but now there are more obvious guidelines for when we stick with native.

The results

Roughly 75% of all mobile development at ClassPass now happens in React Native. Our monorepo and suite of UI tests ensure we are testing React Native code against both apps for all builds. In our Growth squad, almost all mobile development is done in React Native; this has been transformative as almost all mobile work we do improves user conversion. All of this work is done twice as fast as it would be if we still had two fully native codebases. Code can be written once and deployed on both iOS and Android at the same time, and we plow through the roadmap twice as fast as we did two years ago.

I am not a React Native evangelist. As an engineering leader you need to make technology decisions with the best interest of the business in mind. As engineers we are literally building the business and, in the case of mobile app development at ClassPass, React Native was the right tool for the job.