Write once use everywhere with React Native.

React is advertised as ‘learn once write anywhere’ but the problem that i had to solve was to ‘write once use everywhere’.

This article is about the project structure and the practices to follow if your target is to create a react native app that runs on web too(as of now, support for Windows-Universal and OS-X coming soon to React-Native-Everywhere).


Why i did this:

I had a react-native app targeting Android and iOS platforms (with almost 90% common code-base). Life was going good but then came a requirement for a web version of the same with minimum effort and maximum code re-usage. Keeping in mind the requirements and being the lazy person that i am, I was not in the favor of writing all the presentation part of my app again using react-DOM and use other libraries. After searching for a solution to this problem(thinking there might be some noble person on this planet who might have solved this kind of problem) all over the internet, I came across react-native-web, which was built to target front-end web development with APIs that of like react-native at Twitter. So i thought to give it a try and it worked like a charm. Now the problems for me were, local database(needed some ORM kind of thing that supported native as well as browsers or use CouchDB), camera(QR scanning was also a requirement) and routing mostly. So I started abstracting out components and came out a structure with around 70% code common between Native and web. There were a lot of decisions that i had taken and later regretted, so I thought to come up with a starter kit where i don’t have to structure everything again but just clone it and jump on with coding. Exploring a little more, i found out that there are some awesome people out there who have developed similar libraries for developing Windows (universal) and OS-X apps using react-native' APIs. That was when i said to myself “Challenge Accepted”, and decided to come up with a project structure where you can write with react-native once and use it everywhere (on almost all platforms). Redux obviously played a major part in it. I’ve open-sourced the starter-kit and rest of the article talks about what the starter-kit is and why it is the way it is.

Prerequisites:

  • Hands on with react and react native.
  • Overview of git/github, ES6(optional) and webpack.
  • React-Native-Everywhere is the starter kit that we’ll be using throughout rest of this article.

How I did it:

Challenge i had was to make maximum code re-usage of the already built react-native app in order to convert it to a production ready web application.

Breaking into smaller problems:

  • Maximize code re-usage between web and native platforms.
  • I didn’t wanted to use react-dom and found react-native-web(a real life savior).
  • Have total control on all my components(presentation and container) (this article from Dan Abramov might give more clarity).
  • Handle concerns for web and native separately where ever needed(like a persistent database(or use an ORM), routing, rendering, etc.).
  • Make my actions thick(move all my app’s logic to redux actions), so that changing some logic once targets both platforms(web and native).

Lets start with wiring up the project using the boilerplate that i have created to omit the effort it takes to set up a project every time.

Lets start with cloning the starter kit:

git clone https://github.com/amoghbanta/reactNativeEverywhere

Go to the cloned directory and allow npm to do the magic:

npm i //install the dependencies from package.json

Assuming that you have Xcode/Studio configured:

react-native run-ios/android //tell react-native cli to run project

and the starter kit works for native environments. Pure Magic it is(trust me!)

Now lets talk a little about the architecture followed in this starter kit:

Below image is the basic project structure that i follow in my projects(whenever i have to target web with native).

Why I follow this structure?

  • My app’s views and controllers are separated and they don’t interfere in each other’s job.
  • I have a common single source of truth(redux store) for both platforms.
  • I can control how my app looks on native devices and how it looks on a browser separately in different files(i don’t have to use platform based logics, it complicates everything).
  • I can use different routing libraries(for DOM and native) as Navigation APIs of react-native can’t be used with react-DOM.

Moving on, now lets talk about how we are going to target web with this architecture and a minimal effort.

Cool!

What all is used here:

  • react-native-web (this is a super amazing library by Nicolas Gallagher, because of which this all has become a cake walk)
  • react-router (to manage routing on web)
  • react-native-router-flux (to manage routing on native platforms)
  • redux.js (which is optional, but I highly recommend it if you want to keep your life easy)
  • webpack and webpack-dev-server to build our bundle and run app in development mode respectively.

This is what my basic package.json looks like:

package.json

The scripts object has some commands, this is what they do:

- npm start //starts the react-native packager(you won't need it mostly)
- npm run web //this starts our webpack dev server
- npm run build //builds the bundle for the web app ready to be deployed.

Not all modern browsers support/understand ECMA Script 6 or ECMA Script 7 as of now. To tackle this issue, I use babel(to transpiler ES6 to ES5) and webpack to bundle the transpiled JavaScript. Below image is how my basic webpack config file looks like:

webpack.config.js

As you can see in the webpack conifg file, the port on which your development server will run is 9000.(discussion on webpack is another long article in itself, so lets just not detour).

Lets give it a try:

npm run web //inside your project' directory
localhost:9000 //in your favorite browser

A little explanation on what is done here and how:
All the magic done here is by react-native-web and a little tweak in the webpack config:

alias: {
'react-native': 'react-native-web',
},

which replaces ‘react-native’ with react native web anywhere and everywhere in your code base at the time of creating the bundle.
So, all your imports come from ‘react-native-web’ instead, which is actually the DOM implementation(in JavaScript) of the same named APIs(as that of react-native) with a similar implementation and functionality.
Ain’t it really smart?

Conclusion: So, that is what i think how one can use react-native-web to target web along with android and iOS using react native’ APIs and make maximum code re-usage while maintaining a single code base. I’ve been experimenting to target Windows too and shortly will come up with the support for that too in the starter-kit.

All your suggestions and PRs are welcome to React-Native-Everywhere’s github repository.