blog content
Introduction
This article is a bird's-eye view of migrating a React Native library to the New Architecture. At first, I wanted to go into the details. However, due to the pace of changes and ever-evolving new patterns, I decided to walk you through the migration steps with the helpful resources I found.
I had the pleasure of working on migrating react-native-slider and also the Android part of react-native-pager-view.
What’s the New Architecture?
If you are following things that happen in the React Native world, I’m sure you heard about the New Architecture, including Fabric and Turbo Modules.
If you are new to the “New Architecture” world, check out Lorenzo's at React Native Wrocław Meetup this year.
There is also a podcast episode on the New Architecture in which Łukasz Chludziński talks with Nicola Corti, a member of the React Native core team.
What’s Fabric?
Fabric is React Native's new concurrent rendering system, a conceptual evolution of the legacy render system. It allows for all the features React 18+ brings to the table. Fabric is one of the 4 pillars of the New Architecture that React Native core team is working on since 2018.
Thanks to Fabric, instead of using the bridge which communicated with serialised JSON messages, we can communicate using JSI (second pillar), which is a C++ API for interacting with any JS engine. Ultimately JSI is going to replace the bridge (it’s the “bridgeless” project) but for now they live side by side, which is good because it can speed up your migration when you stumble upon some edge case which is not supported yet.
The third pillar is Turbo Modules. It’s an interface on top of JSI, that leverages CodeGen (the last pillar) to generate C++ code from our TypeScript or Flow spec.
If you want to try out the new Architecture in your app make sure to follow this guide.
Steps to migrate a library
- Writing TypeScript spec for Codegen
- Migrating deprecated JavaScript APIs
- Extending <rte-code>react-native-codegen<rte-code> generated interfaces on native platforms (Android / iOS)
- Done ✅
This may seem like a lot of work but with the documentation getting better and more developers getting involved in the process, it becomes easier every day. It is also worth noting that the first two steps can be done even before migrating to the New Architecture - when preparing your library for migration.
Using an official migration guide for library creators
There is an official migration guide that you can follow: React Native New Architecture Libraries. It has examples creating simple Fabric component and also a Turbo Module. There is also another guide for migrating your whole app for the New Architecture.
These examples really help to get you going with the migration. However, most of the time you will end up browsing the source code of other libraries (at least I did). So another helpful resource is a list of already migrated libraries.
Creating a library from scratch
If you want to create a new library or just scaffold a New Architecture setup for your existing library, you can use react-native-builder-bob which recently received a lot of new features!
Here is a screenshot of scaffolding a new library with <rte-code>create-react-native-library<rte-code>. As you can see, there are new options for Turbo Modules.
We have used it to scaffold the New Architecture inside <rte-code>react-native-pager-view<rte-code> which was way faster than creating all the necessary setup by hand. Fabric support will be added soon, but it may be already there as you are reading the article 😄. So go ahead and give Bob a try!
1. Writing TypeScript spec
Now let’s start with the migration part. The goal of this step is to write a specification of your module which is a set of types written in Flow or TypeScript that defines all the APIs provided by the native module. Using a spec allows CodeGen (code generation tool for React Native) to generate native code for each platform during build time. To find more about it, refer to the docs.
Here is how the generated files look like for Android.
It sounds so good in theory. In reality, however, not everything is working as expected (yet!). Let’s go over a few issues I’ve stumbled upon.
Unsupported props between platforms
As for now CodeGen doesn’t support platform specific specs, so keep in mind that you need to define types for every platform in your spec file. If you have any props that’s not supported on iOS it’s not a problem because you can skip it inside <rte-code>updateProps<rte-code> method:
snippet source code
However, Android handles props a little bit differently. It generates setters for all of the props which need to be overwritten.
The best way to tackle this problem is to override those setters with empty functions like here:
Union types
Typescript union types are not yet supported by Codegen, but we can get around this limitation by using a Codegen helper type named <rte-code>WithDefault<><rte-code>.
Here is what this “hack” looks like in <rte-code>react-native-pager-view<rte-code>
If you ever get stuck, you can go through these resources:
- <rte-code>react-native<rte-code> source code (features a lot of fabric components)
- Working Group for the New Architecture - features a list of already migrated libraries.
It’s also worth mentioning that Meta is working on making Codegen support more advanced.
2. Migrating deprecated APIs
Both projects I’ve worked on haven’t used any of the deprecated APIs. However, one of the most used ones is <rte-code>setNativeProps<rte-code>. It fundamentally breaks the model of the New Architecture leading to skipping all the rendering steps and desynchronising UI between React Native and what’s shown to the user. You can read more about it on github.
If you happen to use one of the APIs from this list though, it should be pretty straightforward to just replace them. You can find helpful resources in the Working Group discussion on the New Architecture.
3. Implementing Codegen generated interfaces on native platforms (Android / iOS)
This step is probably the most time consuming and it depends on the complexity of your library.
Android part is easier since we are having two source sets (<rte-code>newarch<rte-code> / <rte-code>oldarch<rte-code>) which get loaded accordingly to a <rte-code>newArchEnabled<rte-code> flag in <rte-code>build.gradle<rte-code> and then a one <rte-code>SharedImpl<rte-code> file which you can share between implementations.
However, iOS is based on creating a new <rte-code>.mm<rte-code> files which are <rte-code>implementing<rte-code> Codegen generated protocols. Depending on what’s your library doing you may need to create separate implementations for new / old arch or share code between them.
Using Swift
Both of the libraries I’ve worked on haven’t used Swift. But, unfortunately, it’s not officially supported at the moment. The official Meta team statement is that they are “looking into it.” Codegen generates native ObjC++ files (<rte-code>.mm<rte-code>) which are tricky to reference from Swift files.
There is a great twitter thread from Mateusz Mędrek on his experiences.
Using Kotlin
When we were migrating react-native-pager-view I had lots of build issues which resulted from using Kotlin. Due to mixing Java with Kotlin, Codegen created files which were not included. So we have added a temporary workaround which may be useful in case you are also using Kotlin in your library. Check it out here:
snippet source code
Custom component state with C++
In some use cases Codegen isn’t enough - it’s a good starting point but It wasn’t designed to cover all the edge cases. So if your native component would like to update its frames in a synchronous way, you will need to add custom C++ state.
I haven’t added custom shadow nodes to <rte-code>react-native-slider<rte-code> in C++ yet (it’s scary but it’s on my to do list 😄). We have lots of great developers working on improving the New Architecture here at Callstack. My colleague Piotr Trocki together with Nicola Corti created a sample repository which will help you set this up. Right know the guide only covers Fabric, yet there is already an open issue to support Turbo Modules.
If you are afraid of C++ you can make use of the good old bridge as it’s still being initialized. But keep in mind that one day there will be bridgeless mode added.
Build times
While I was working on migration of <rte-code>react-native-slider<rte-code> we were using react-native <rte-code>0.69<rte-code> which had a major flaw in terms of build times. When it was time to create a fresh build without any cache, I could go and make myself a coffee ☕️ and then go for a walk. It took around 15-20 minutes. The good thing is that the community have added an <rte-code>--active-arch-only<rte-code> flag which will decrease your build times even 3x. And the core team is actively working on bringing the build times down by leveraging prebuilt artifacts outside of NPM.
Also keep in mind that you will need a pretty good machine to play around with new architecture. I saw few comments to my tweet indicating that building app (using new architecture) took them around 30 minutes. Of course once the build is cached it will be faster.
4. Now it’s time to release a migrated library
Every migrated library brings us closer and closer to finally enable the New Architecture in the production apps. Meta is using Fabric in their Facebook app since last year, but most of us are probably still blocked by the libraries that don’t have support for the New Architecture yet. If you want to track the status of migration for libraries, there is a dedicated discussion page. And if you are working on migrating your library, also add it there so it will be listed in the React Native Directory as a library that supports the New Architecture.
Try React Native Slider with Fabric
- Create a new app using React Native CLI: <rte-code>npx react-native init NewArchApp<rte-code>
- Add a React Native slider <rte-code>yarn add react-native-slider<rte-code>
- [iOS] Run <rte-code>RCT_NEW_ARCH_ENABLED=1 pod install<rte-code> inside ios folder
- [Android] Set <rte-code>newArchEnabled=true<rte-code> in <rte-code>gradle.properties<rte-code>
In the New Architecture mode, components that are not yet compatible will show a red box: <rte-code>Unimplemented component: <ComponentName><rte-code>, and you will likely notice them. In that case, please let the library maintainers know about it.
Summary
Even though the React Native New Architecture is out there for a good while now, it’s still a challenge to do migrations, especially Fabric. The React Native core team put a good amount of work into making this effort easier. There’s a lot new documentation and up-to-date repositories on how to migrate both libraries and apps. And it’s still not enough. We keep finding the gaps in the official docs and often need to dive into the source code and commit history to figure out the rationale behind some API decisions.
Nicola Corti from the core team frequently mentions at conferences and podcasts that now it is the best time for apps to migrate. That’s because now they have a dedicated capacity to help the community solve their app and library problems in a close cooperation. They can’t promise whether they’ll have this capacity in a year from now.
At Callstack, together with my fellow developers, we often took this opportunity to reach out directly to the core team members, be it around the New Architecture’s autolinking, or migrating Slider, PagerView and Bob. And they are always utterly helpful, even though not available on-demand.
Although this may not be the best time for your app to adopt the New Architecture, I strongly encourage you to try. Turn on the build flag, check which components and modules are missing support. Post your problems and findings in a github discussion group. The New Architecture is the biggest thing that has happened to React Native since its inception. Get involved 🙂