Building a platform agnostic app (react native and web.. react-native-web) 📱 💻 🖥 📺

Or Yoffe
5 min readJan 16, 2018

--

**************************************

Update: I wrote a new cli tool to get you up and running with react-native-web in no time.

create-react-native-web-app 🎉🎉🚀
https://github.com/orYoffe/create-react-native-web-app

**************************************

When I joined my last company, they had a specific request for me, to build a shared components and business logic frontend app for IOS, Android and Web. Since I’m a frontend developer who loves new areas, I couldn’t say no and I had to jump at the opportunity.

We are a bit lucky 🍀. Since our product doesn’t need to support many web-specific features, we are able to develop an app with a subset of tools for our costumers. Meaning that we can do whatever we want in the background. I would suggest reviewing your requirements, checking whether these tools solve all of your needs, before starting to implement this approach into your project.

I only did an example React Native app before (uncompiled and unpublished). I didn’t know much about React Native. I only heard of a repo on Github called “ReactNative for Web” https://github.com/necolas/react-native-web. If you haven’t heard of it, I recommend watching this talk: https://youtu.be/tFFn39lLO-U

After a bit of digging in to articles and trying to implement each environment in its own realm.. I found that for me, the best starting point was a great react native template, called “re-start” https://github.com/react-everywhere/re-start which is based on React Native for Web.

The react-everywhere template had some very useful solutions, as it was made from create-react-app and react-native combined ♻️

Routing = react-router (react-router-dom || react-router-native); The package offers almost the exact same router api for both platforms. All you have to do is require the right one according to the Platform.OS variable and export the right Router

import { Platform } from 'react-native';const isWeb = Platform.OS === 'web';const RouterPackage = isWeb ? require('react-router-dom') : require('react-router-native);
const Router = isWeb ? RouterPackage.BrowserRouter :
RouterPackage.NativeRouter;
export Router;
export default RouterPackage;

Compiling and running the app. Webpack configurations used only for web (uses an extended version from the create-react-app repo). On the native side, react-native does its own compiling so it’s the same as running a react-native app. 📲

Of course not everything fixed itself in the front of tech. I had some days on which I sat there all day long trying to work out bugs which I had never seen before 😵 (XML, Objective-C and Java). Also, adapting to build react native UI from a background of css is not the fastest slide 🎢.

Some issues remained between Native and Web:

  • Babel
  • Testing
  • Native features/components

Babel is needed to compile to ES5 for the web, so the configurations needed to be separated for Native and Web, though both use the "react-native" preset. Luckily Babel’s preset env feature solves this issue by setting your process.env.BABEL_ENV to your desired config.

// before running process.env.BABEL_ENV = 'test' or 'test:native' 
// or whatever you config needs before babel starts
// and the main difference is that web config
// needs the "react-native-web/babel" plugin
{
"presets": ["react-native"],
"env": {
"test": {"plugins": ["react-native-web/babel"]},
"development-web": {"plugins": ["react-native-web/babel"]},
"production-web": {"plugins": ["react-native-web/babel"]},
"test:native": {},
"development": {},
"production": {}
}
}

Jest and testing was a big requirement for our new project and was really important to us and it was discussed that we would want to do TDD as early on in the project as possible, so we had to have it working ⚙️

Rendering Native Components for Testing didn’t work from the start so after a while I realised I had to separate the jest testing config for native and web. This was easy enough with their --config cli option to pass the path to the config file.

$ jest --config './path/to/jest/config'

Main differences between the configs were:

// Native config
{
coverageDirectory: 'coverageNative',
preset: 'react-native',
}
// Web config - made native build crash :/
{
"moduleNameMapper": { "^react-native$": "react-native-web" },
}

My main issue with testing (that took me the longest to solve) was snapshot collisions. If you haven’t tried Snapshot tests with jest, I highly recommend it 😍

I had to separate the snapshots, instead of duplicating the snapshots tests for each platform with many if statements. Also, every snapshot that jest doesn’t use, it considers as obsolete and will remove it on update 😣. So all of my Native snapshots would be removed when I update the Web snapshots.

duplicate snapshot tests

I ended up writing a script to switch the snapshots between three folders before each platform is being tested. Which made all my component folder structures to look like this in the end:

/Button
/__native_snapshots__
/__snapshots__
/__web_snapshots__
Button.js
Button.test.js

Enabling me to write the snapshot tests once in each test suit, instead of twice with the platform name in the snapshot header

The rest of the tests were almost the same as web testing, thanks to react and the great tools from Enzyme, Jest and react-test-renderer. You basically test JS and react as you would normally. Here’s a part of an example with Enzyme:

a few small differences than normal web component testing

After a while we needed some Native features and components. Since we wanted to support several languages we needed a language picker (like a dropdown).

In Web, a simple <select> tag and in React Native you have a native Picker component. Since this is all based on react-native, to create html tags is a bit backwards but doable with the createElement function:

WebPicker component, compiling to a select tag
NativePicker component, compiling to ios and android

After some time, support and patience from my new company and coworkers, the app was ready to be developed on. The results on Github https://github.com/VISI-ONE/rnw-starter-app 🎉 :

IOS & Desktop Web

A word of caution: These tools are not all easy to assemble and you will have to dig in to Xcode and Android Studio from time to time and see other languages that you might not be familiar with, if you’re coming from web. For me it was a challenge and one that I am happy I took on 🙂.

This is an amazing age in Frontend, where a web developer can cross over with good performance to different platforms and clients with the same business logic. I hope we will see more of these tools and use cases, also because I love to hear from my javascript friends that they’re doing Server side, TV apps, Desktop and so on with JS ❤️

👏 👏 👏 Thanks:

--

--