Please note that the ReactXP library is no longer maintained and has been deprecated, so some information from this post may be out of date.
Our adventure with ReactXP started several months ago, when me and my colleagues began working on a new client project. This one was different from our typical projects and really exciting—one of the main requirements for the app was to make it available for iOS, Android, and the Web with nearly identical feature sets and very few design differences. Our three-person-strong project team had extensive experience with JavaScript, but we haven’t worked on any native mobile apps using Objective-C/Swift or Java. It felt just right to use a cross-platform solution for the job.
Providing a smooth cross-platform experience with a single codebase might seem like an impossible task. So far, there have been several solutions to this problem—although each one requires some compromises:
- RubyMotion, Xamarin, and other platforms compiling higher-level languages to native bytecode: Even though they might appeal to Ruby or C# developers aiming for iOS/Android, they still require a Web app to be implemented separately. Some of them also require a paid subscription.
- Hybrid Apps (Cordova/PhoneGap, Ionic, etc.): Those allow sharing of not only the business logic (JavaScript) between Web and mobile, but also the layout (HTML) and styling (CSS). Since a hybrid app is essentially a Web app wrapped in a mobile browser runtime with native plug-ins, there is always some performance overhead—the computing power of modern devices, however, has been steadily eroding it.
- Progressive Web Apps (PWA): A more lightweight alternative to Hybrid Apps—these are also regular Web apps, but instead of relying on third-party platforms and plug-ins for access to native features, they make use of modern browser APIs.
- Flutter: An open-source, free framework created by Google, designed to build mobile apps on iOS, Android and Google's new platform - Google Fuchsia. It offers a lot of widgets and tools for building beautiful UIs.
- React Native: Here, the business logic is written in JavaScript and executed in the native JS engine, whereas the layout and styles are compiled to native iOS/Android UI elements, providing superior performance to browser-dependent solutions. Therefore, while the business logic can be shared between Web and mobile, layout and styling need to be implemented separately for the Web using HTML and CSS.
In the past, programmers rarely had good things to say about cross-platform solutions especially if they had been doing native iOS and Android development. Things are changing these days, however, as some of the modern frameworks have already managed to solve the performance problems plaguing them.
Along Flutter, React Native might seem like the best compromise between performance and code sharing. It’s still far from ideal if you are aiming for a truly cross-platform, single-codebase app, but we kinda loved the idea behind RN: “Learn once, write anything.” That means that you can share some amount of the code, but need to build some platform-specific modules for iOS and Android.
To counter the problem of UI code sharing with React Native, several open-source libraries have recently showed up—most notably, React Native For Web by Nicholas Gallagher and Microsoft’s ReactXP. Both of them build upon React and React Native, aiming to provide equivalents of React Native components (i.e. View, TextInput, etc.) for “regular” (i.e. browser-based) React. Both also feature several helpful abstractions over common Mobile/Web features, such as key/value storage or network status.
Why We Chose ReactXP
When we started a discussion about technology solutions for the project, we thought that we would develop the mobile and Web apps separately. Even though the design did not differ a lot between the mobile and Web versions, there were significant differences in behavior.
Therefore, we assumed that many components or views would still have to be implemented separately if we used a cross-platform solution, which would make the code hard to maintain.
Then, however, we took a closer look at React Native for Web and ReactXP as potential single-codebase solutions. Even though React Native for Web seemed more popular and had more GitHub stars and online resources dedicated to it, we ultimately decided to go with ReactXP for two reasons:
- It was created by Microsoft as a building block for their brand new, cross-platform version of Skype, and open-sourced in April 2017. The new Skype has since been released—ensuring us that the library is production-ready and won’t be going away anytime soon.
- It is written in TypeScript, which has been our language of choice for the project. In addition to the obvious benefit of static typing, combined with tools like TSLint and Visual Studio Code, TypeScript gives us a great developer experience with code completion (a.k.a. IntelliSense) and error checking. With ReactXP, these benefits extend to markup and styles as well—since the whole library is typed, available component props or styles are hinted, and any mistakes are caught instantly while editing!
Of course, that doesn’t mean that React Native for Web is an inferior solution—it’s been successfully used in popular apps and websites, including Twitter or Major League Soccer. It is also well-documented and supported, with new releases following the latest versions of React Native. It may also be easier to migrate existing React Native code to use React Native for Web, as it is more similar to pure React Native. Probably the biggest difference is the lack of TypeScript support in React Native for Web.
(Mostly) One Code to Rule Them All
So, how did we achieve a single codebase for iOS, Android, and the Web?
To start out, the ReactXP GitHub repo features several sample apps, from the simplest Hello World to more complex examples making use of various components and extensions provided by the library.
Basing off of those examples, we figured that it makes the most sense to start with separate entry points for each platform (e.g. app.native.tsx
/app.web.tsx
). Going down from there, we have navigation configured separately, as React Navigation and React Router currently seem to be the best navigation libraries for native and Web respectively. And, honestly, it seems unlikely that a good cross-platform solution will emerge anytime soon, as navigational patterns are generally quite different between native and Web apps.
Once we get to the level of a specific view (screen for native / route for web), we import a common container and true single-codebase goodness begins.
Using cross-platform components provided by ReactXP, we are able to define all views only once—they look and behave as expected in both mobile and Web versions. If we need some simple platform-specific overrides, the library provides a handy API (RX.Platform.getType()
) to detect the platform, allowing us to set up different behaviors depending on its type.
In more complex cases, we have created separate native and Web components, which are then imported using path aliases. This works thanks to separate tsconfig.json files and separate webpack configs for Web and native—each of them defines different resolution paths for specific aliases (e.g. @components
resolves to src/components/web
or src/components/native
).
At build time, Web-specific components are imported for the Web app, and native-specific components for native apps (we have swapped the default React Native packager for the excellent Haul packager, which allowed us to use webpack for native builds).
Is ReactXP viable?
This is it. Using ReactXP allowed us to develop an app for Android, iOS, and Web which means that we actually got the Web app for free. After more than six months of using the library, we’re pretty impressed with it and haven’t encountered any problems during this time. We can safely recommend it to anyone developing a cross-platform, mobile/Web application.
If you are curious as to how we approached the more specific technical issues, stay tuned for more in-depth articles on React Native and ReactXP.