Migrating React and Native Apps To React Native - Everything You Need To Know
This article guides you through migrating native or React web apps to React Native, explaining two approaches: greenfield (building from scratch with speed and risks) and brownfield (gradual migration with less risk but a longer timeline). It emphasizes the importance of planning, features mapping, and documentation. The article also touches on converting React web apps to React Native, stressing the need for platform-specific components. Success factors include smooth navigation, code reuse, and maintaining a native feel. Clean code, team preparation, and understanding React Native's value are highlighted for a successful migration.
If you are here, you probably know why it is good to migrate your apps to React Native.
But, no matter if:
- you are at the planning stage,
- you’ve already started your migration and something went wrong,
- or you want to evaluate if you migrated your app well,
it’s good to take a step back and listen to what experts have to say about it.
Migrating from native to React Native
The migration process can be done in two ways: with a greenfield or brownfield software development. Let’s go through those two approaches, point out the main differences between them, and describe pros and cons of each one to make you choose the right approach for your product and team.
What is greenfield software development?
In short, greenfield development involves creating an application in a totally new environment, from scratch with no legacy code around. In this approach, we start fresh with no restrictions or dependencies. This approach allows us to migrate our app much faster than with the brownfield approach but also carries some risks that cannot be overlooked.
Potential risks of greenfield development
Usually you don’t want to “kill” the native app and then build the React Native from scratch. It’s much better to let the “old” app do its job until the new, cross-platform one is ready to take its place. And this maintenance of the “old” app accompanied by the development of the new one can become overwhelming for your team. In consequence, the new app can have more bugs and errors than it would with a well-rested dev team. That’s why migration with a greenfield approach should be carefully thought out and planned in great detail, with a special focus on the features map.
What should you keep in mind while planning migration with a greenfield approach?
When planning a migration to React Native with the greenfield approach, it is crucial to create a features map. It takes a lot of effort (and, in consequence, money) but will not allow you to forget about any feature while working on migration.
This is what you should start from - take a look at your current app, create a features map and a solid documentation of the project.
Typical problem we encounter in the wild which slow down the migration are:
- The code has no official documentation.
- Comments in code describing the code but not the reasoning behind it.
- Documentation is scattered across the teams.
- It’s hard to find a single owner of a feature or the product as a whole, sometimes there are multiple people (managers, developers, etc.) that only combined have a whole picture.
- Unused legacy code, obfuscating the feature contents.
What we suggest is to design user stories - an informal, natural language description of one more feature of a software system. User story describes the feature from an end-user perspective, the type of this user, what they want and why. Simply put, user stories are stated ideas of requirements that express what users need, the “to-do” lists that help developers determine the steps along the project path.
The main purpose of the user story is to define how particular feature, functionality, etc., will deliver a value back to the customer.
Most of the user stories follow the same, simple scheme:
The role defines the end-user, an actual human who interacts with the system. It should be described as specific as possible.
The action stands for the behavior of the system and should be written as an action. In the action part, the system is implied and does not get written in the story, which should be written in an active voice.
The benefit should present a real-world result of the action and describe the value behind the interaction with the system for the users.
Well-prepared and thought out user stories help us to stay focused on end users, their real needs and expectations towards our app. Thanks to the simple and consistent format, user stories allow developers’ teams to deliver quality software faster and better satisfy their end-users’ needs.
There is a saying that the written code is a reflection of the team that wrote it. Food for thought.
Greenfield - pros & cons
Wrapping up all the pros and cons of greenfield software development:
- Greenfield is much faster than brownfield.
- Can be started with a predetermined direction.
- A lot of possibilities to add improvements to existing tech solutions.
- Requires more effort from the team.
- High risk of bugs and errors.
- Can cause difficulties while choosing the right approach with so many development possibilities.
- Focuses a lot of development forces on one project.
- Risk of “people problems” - dividing team into “legacy app team” and “new app team” usually slows down feature development and may also be a “people problem” (if the legacy team is not comfortable working with legacy tech for a while).
Brownfield software development seems to be a great approach to migrate apps from one framework to another because it allows to integrate new features (cross-platform in our case) with React or native apps maintaining their business continuity.
Speaking of which, brownfield allows us to gradually transition native apps into cross-platform ones feature by feature, screen by screen, without rewriting the app from scratch. We can integrate new inclusive cross-platform components into a working app and replace its existing native or React pieces one by one. What’s the best here, common cross-platform features work the same on all platforms with negligible impact on performance.
Planning migration with a brownfield approach
When you migrate adopting a brownfield approach, you need to be aware that the whole process takes longer than rewriting an app from scratch as in the case of the greenfield approach. In brownfield, you will have to use the knowledge about the native platform from which you are migrating from and the architecture of the whole product to know how to “plug” into this architecture or create some bindings between the architecture and the migrated part.
Also, during the migration, it may be necessary to share some data between React Native and native which also may cost you much work and effort. Whereas, when it comes to the features map - it’s more like a “nice-to-have” that allows to rewrite new screens incrementally rather than a necessary part of the plan. With this approach the map, or documentation, can even be created incrementally alongside the migration, but prior to dev work.
To sum up the main differences, with the brownfield approach the changes are smaller and the whole process takes longer when compared to greenfield, but the risk of bugs and errors is much smaller and they are easier to find and fix during rewriting the screens.
Brownfield - pros & cons
Wrapping up all the pros and cons of brownfield software development:
- Once written cross-platform component works the same on every platform
- No risk of inconsistent platform-specific features.
- Gradual migration process with no harm for the business continuity of the app.
- Lower development costs - one team works on app for multiple platforms.
- Live and OTA updates. Add features and updates without going through the app store update cycle.
- Takes longer than with greenfield approach.
- Requires native knowledge to properly proceed the project.
- Can be more expensive in the final count.
How Callstack uses brownfield
Over the years, by solving a list of our clients’ issues and developing an Open Source library dedicated to brownfield development, we have mastered our own approach that has proven to provide the best possible developer experience with the least amount of effort.
We usually tackle components screen-by-screen and register them with the “AppRegistry.registerComponment” API. Thanks to that, the native interface can locate certain React Native components and load it with appropriate initial props. Then the certain set of components is packaged in a platform-specific way to ensure maximum integration that does not depend on React Native.
On iOS, we want to generate a static library (file with “.framework” extension) with all wrapped components being part of the public interface. On Android, the process is similar and results in a “fat aar” file. Thanks to that, native developers can use components that are part of the public interface without explicitly depending on React Native - no “node_modules” are needed and developers don’t even need “node” to be installed on their machines. In most cases, such solutions are used for embedding React Native screens within native apps, which is the most common brownfield strategy for migrating from fully native apps to React Native ones.
But, at the end of the day, everything is a React component. Thus that abstraction can be further extended to enable additional strategies for sharing complex components between different environments and providing layout consistencies.
Thanks to that, Android and iOS developers can move faster by composing the interface from existing UI components that work and look the same way in both native and React Native apps. This guarantees consistency within applications created with the brownfield approach, where both native and React Native screens co-exist for the period of the migration, as well as with other fully native apps, where React Native implementation is not on the roadmap yet.
React web to React Native. Migration or conversion?
As we’ve seen a lot of queries regarding migration of web apps built in React to React Native, we decided to put some light on it.
First of all, it is not a type of migration as we described above. It is more like expanding your React web app with cross-platform abilities. So, in this case the word “conversion” is more proper than migration.
Most of the React web apps out there are written with platform-specific “host” components used right next to composite components. This makes things easy for web developers, because the JSX we write resembles HTML a lot.
However, with this approach we’re not fully utilizing the possibilities that JS bundlers present to us in terms of platform-specific code. The MyButton component can only be rendered in the environment that understands <rte-code>`div`<rte-code> and <rte-code>`span`<rte-code> elements, e.g. DOM, or testing environment, but nowhere else.
If we converted this component to embrace React’s component model:
we could render it on virtually any platform that React supports, e.g. Android or iOS thanks to the React Native renderer. We’d implement our leave components, like <rte-code>“SecondaryText.js<rte-code>”, rendering a `span` element by default. And provide additional implementation for React Native in <rte-code>“SecondaryText.native.js”<rte-code>. A bundler that we use, be it Metro or Webpack, will be smart enough to import the correct implementation for a specific platform.
And that’s how we go with every component that has HTML declared and web APIs used.
You may think that’s “too much overhead” or “why would I create extra components”, but listen to Sebastian Markboge for a moment:
That’s just React. And it’s not only beautiful, but also immensely powerful.
Every time we use a platform-specific component which is not from the lowest level of the component tree, like part of the icon, button, etc., and we use it in inappropriate places in the app, we increase the scope of the future migration on every platform which is not web.
Hence, there were some popular solutions that allowed developers to not rewrite any components in React (or any other library) and still render the app on mobile. Even so, it wasn’t the “classic” app but only a WebView - a small browser where the web app was working.
The same applies to the electron apps like Discord or Slack. There are web apps that render on the browser with the help of electron so there is no need to rewrite their components in React.
On the flip side, if we started our web app with React Native (thanks to the <rte-code>react-native-web<rte-code> project), we wouldn’t need such big rewrites at all. We’d only need to adjust our components here and there to render as expected per platform, but it would work out of the box. That’s because React Native renderer doesn’t allow any magical host components like <rte-code>“div”<rte-code> or <rte-code>“button”<rte-code> in its JSX.
All platform-specific components, like View or Text, need to be either imported from the “react-native” package or from 3rd party native components.
And the “place” we import from is a subject to aliasing for our bundlers. We can configure Webpack to alias <rte-code>“react-native”<rte-code> to <rte-code>“react-native-web”<rte-code> which understands Views and Text and provides web implementation.
One of the trickiest problems to solve when converting web apps to React Native is styling. If you’re using Styled Components or Emotion, you’re partially in luck, because they support React Native. Why partially? Because the overhead in creating extra layout components is noticeable. It’s usually negligible, but you may need to jump into the profiling tools more often.
If, against all the good practices out there, you styled your app with inline styles, you’re in luck as well! React Native supports it. It has a performance impact on composite components, but it’s nothing that you couldn’t fix for the most annoying places.
If your app, however, is styled with CSS (or SCSS or else for that matter), you’ll need to reimplement the styling from scratch. Thankfully, you can automate a lot of it thanks to Styled Components’ css-to-react-native library.
We highly recommend watching this great talk from Radek Pietruszewski of Nozbe on sharing code between React and React Native apps:
Again, if we started with React Native model (through react-native-web), we almost didn’t need to worry about styles at all. Almost, because native and web platforms are different and need special handling in some cases. But the majority of “static” styles could stay intact.
Migration to React Native - summary
To sum everything up, basing on our successful migration of the Kiwi.com app where we used a brownfield approach, we can distinguish the following success points of the migration to React Native:
- React Native screens load without delay.
- Navigation works as expected.
- Native code can be reused in React Native.
- Everything in one repository and React Native version in sync.
- The app keeps a natural native feeling and, when we expand React web app, it doesn’t have to rely on WebView.
To achieve that, we need to prepare a detailed plan of the migration including features map (must have in greenfield approach), and have a clear idea of what we want to achieve, in what time and how much effort we can dedicate to the migration process.
Also, it is important to prepare a solid and detailed documentation of the migrated app before developers start working on rewriting screens. Without clear app structure, the risk of bugs or even accidental omission of some features increases which leads to increase in costs of the migration.
To make full use of all the advantages of migrating native or React app to React Native, we need to remember to “clean up” the app from the legacy and migration-only code because if we won’t, the app may not work properly and have serious performance issues.
Another important factor is that your team has to be prepared for the migration, know and understand why you are migrating your product to React Native and what will be their role after this process. It is essential for them to understand the value that React Native brings to the table, especially the circle of technologies close to React Native. Such things as ease of implementing new cross-platform features on every platform at once and wide range of Open Source libraries that provide great support for developers in their work.
If you are curious about migration to React Native, greenfield and brownfield software development, check out this episode of The React Native Show podcast fully dedicated to migration from different technologies to React Native:
As described above, migrating native and React apps to React Native is a very challenging, complex task that requires wide knowledge and rich experience not only in React Native, but also in native and, in some cases, web development. Luckily, the Callstack developer team is one that has it all.
As a community-trusted team of React Native experts, experienced in man difficult and demanding projects including migrations from React web and native platform to React Native, and creators and maintainers many Open Source libraries useful in migration process like Paper or React Native Brownfield, we are always eager to help at every stage of your migration process.