React Native Web & Isomorphic magic

One of the most appealing qualities of React Native is the idea of isomorphic code. Code, that can run on both iOS and Android, enabling you to write 2 apps at the same time.

But, there is another benefit to isomorphic code. You can also speed up writing your code just for one app.

Since react Native is using React code, I initially wanted to develop parts of the application “the React way”, using hot reloading, React Devtools and the fast feedback cycle that web development permits. That would let me work on the app without emulators and building the code.

It’s possible and much more.


‘react-native-web’ builts your react native app as a web app. Not pieces and snippets, but the whole app. Granted, there is a lot of functionality missing, so it’s almost certain that you will have to modify your source a bit to even make the build work, but there is one use-case that it’s perfect for:

UI components.

In TeamParrot I have separated all UI components as “dumb components” without much logic nor dependencies. That way, I can develop UI separately from the whole app. I have made specific container-component for web that holds all of these dumb components and allows me to see all of them in one place, iterate and work on in my web browser.

Let’s do this!

If you want this also for your app, here is how I’ve done it for TeamParrot:


I used react native web boilerplate called react-native-web-starter to get started. I downloaded the latest master, dumped the files into root of TeamParrot project and… Build failed. Now web, ios, and android build stopped working.

Now, let’s fix it!

First need proper libraries in your package.json devDependencies:

"react-native-web": "^0.0.39",
"babel-core": "6.9.1",
"babel-loader": "6.2.4",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.5.0",
"babel-preset-stage-1": "6.5.0",
"babel-runtime": "6.9.2",
"webpack": "1.13.1",
"webpack-dev-server": "1.14.1"

You need to remove .babelrc provided by react-native-web-starter, since that will interfere with RN babel build process. But the configuration from .babelrc needs to end up somewhere, so you need to modify your package.json loader to include:

    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    query: {
        cacheDirectory: true,
        "presets": [
        "plugins": [

Everything seems ok at this moment, but the app still does not want to load. After debugging for a while it turned out that these components didnt want to play nice with React Native Web:

  • Switch used for toggles
  • Icon from `react-native-vector-icons`
  • RefreshControl used for “Pull to refresh” on iOS.

I decided on a n ugly solution that I would just render “Text” instead of Icon and not render Switch or RefreshControl at all by passing “webDevelopment” prop:

export default function MainView( { queue, playing, channels, stop, pause, play, refresh, loading, header, openDrawer, webDevelopment = false } ) {
	const msg = get( queue, [ 0, 'items', 0 ] );
	const scrollProps = { style: { flex: 4 } };
	if ( ! webDevelopment ) {
		//When building this component inside react-native-web, RefreshControl does not work correctly
		scrollProps[ 'refreshControl' ] = ;

The last problem I needed to solve was loading images. React Native has a an Image component, to which you pass local images by require prop:

To make this work on web, with webpack, you need to install file-loader:

npm install –save-dev file-loader


it’s located under /web/ :

const path = require('path')
const webpack = require('webpack')

const DIRECTORY = path.join(__dirname)

module.exports = {
    devServer: {
        contentBase: path.join(__dirname, 'src')
    entry: [
        path.join(__dirname, '../index.web.js')
    module: {
        loaders: [
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    cacheDirectory: true,
                    "presets": [
                    "plugins": [
                test: /\.(png|jpg|gif)$/,
                loader: "file-loader?name=img/img-[hash:6].[ext]"
    output: {
        filename: 'bundle.js'
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin()
    resolve: {
        alias: {
            'react-native': 'react-native-web'

Actual web code

This is my index.web.js:

import { AppRegistry } from 'react-native'; import Screens from './web/screens';

AppRegistry.registerComponent('App', () => Screens); AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') } );

Let’s roll!

And this is how I run my webpack dev server:

webpack-dev-server --port 3000 --config web/ --inline --hot --colors --quiet

Zrzut ekranu 2016-11-12 o 19.37.20.png
Beauty of developing React Native app in devtools

Try it! It really speeds up the whole process!

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

[display-posts tag=”React-Native-Weekend” wrapper=”ul”]

React Native: Oauth

If you want to use any self-respecting API on the web, you are going to need Oauth authentication. If you need an explanation what oauth is, there are plenty better ones. These are the steps your app needs to go through:

  • Send user to special URL in the browser that lets them log in to the service
  • Handle callback URL from this service
  • Exchange keys with the service to make sure callback was legit
  • Store the token.

Since I used Slack API from the slack npm package, I had the “Exchanging the keys” parts ready.

0. Preparing the app

You thought we were going to start from 1, didn’t you? Well, you you must be new to this “programming” stuff. 😃  On the more serious note, you need to register your app in the API provider to get special keys for the step 1 and fill in URL for the step 2.

For TeamParrot, I set up following OAuth redirect URL:

Zrzut ekranu 2016-10-30 o 16.29.31.png

1.Send a user to the special URL

You will need to use “Linking” library. DO NOT confuse Library called “Linking” with the process of “Linking libraries”, which is tying native libraries to JS code. I just send my users to '' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' and that opens web browser with appropriate “back” button.

2. Handling callback URL

If all goes good and your users give you appropriate permissions, they will be sent to the URL you specified in step 0. You may notice that this step has a weird protocol (team-parrot://). It actually can have any protocol you want that’s not used by an other app. This is enabled by “Deep linking” and “Intents” On android.


First, you need to configure your app to handle this specific link you want to capture. That tells the operating system to route those links to your beautiful and shiny app. As per usual, you have to do that in info.plist :


For a reason I completely do not understand, you also have to mess with a bit of Native code. More info in the docs

So, in file ios/SlackBird/AppDelegate.m :

@implementation AppDelegate
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
+  return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];


Deep linking is called “intents” in Android and much of the same concept applies. You have to tell the OS that you want to open certain protocol inside your app. So:

In android/app/src/main/AndroidManifest.xml

<activity android:name=".MainActivity"
+        android:launchMode="singleTask"
+        <intent-filter>
+          <action android:name="android.intent.action.VIEW" />
+          <category android:name="android.intent.category.DEFAULT" />
+          <category android:name="android.intent.category.BROWSABLE" />
+          <data android:scheme="team-parrot"/>
+      </intent-filter>

OAuth walkthrough

Now you are ready to become a Oauth Sith and step to the dark side. This is how you oauth:

Linking.openURL( '' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' );
Linking.addEventListener( 'url', handleUrl );

And now your function “handleUrl” will be responsible for confirming “CODE” and exchanging it for a token. Usually, that will be handled by your API library of choice. Here is how I did it for TeamParrot:

import { client_id, client_secret } from '../config/slack';
import exchangeCode from 'slack/methods/oauth.access';
import testAuth from 'slack/methods/auth.test';
import { Linking } from 'react-native';

export function login() {
    return new Promise( ( resolve, reject ) => {
        Linking.openURL( '' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' );
        Linking.addEventListener( 'url', handleUrl );
        function handleUrl( event ) {
            const error = event.url.toString().match( /error=([^&]+)/ );
            const code = event.url.toString().match( /code=([^&]+)/ );
            if ( error ) {
                reject( error[ 1 ] );
            } else if ( code ) {
                exchangeCode( { client_id, client_secret, code: code[ 1 ] }, ( err, data ) => err ? reject( err ) : resolve( data ) );
                Linking.removeEventListener( 'url', handleUrl );
            } else {
                reject( 'params' );
    } );

export function testLogin( account ) {
    return new Promise( ( resolve, reject ) => testAuth( { token: account.access_token }, ( err, data ) => {
        if ( err || data.user_id !== account.user_id ) {
            reject( err, data );
        } else {
            resolve( Object.assign( {}, account, data ) );
    } ) );

So there you have it. Now, you should be authenticated over Oauth2 with the slack API and able to make calls.

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

  • Oauth
  • Vector Icons – FontAwesome
  • CodePush
  • Renaming an App
  • Web & Isomorphic code development in Chrome devTools
  • Whole story

[display-posts tag=”React-Native-Weekend” wrapper=”ul”]