April 4, 2018
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.
Using ReactXP and React Native allowed us to develop a native mobile app for two platforms and a Web app simultaneously using one codebase, essentially giving us the Web app for free. And in this article I'm going to tell you a story behind it.
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:
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.
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:
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.
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.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
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).
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.