Sharing Code between React.js and React Native

My first HTML 5 game Basic Pairs is one code base for both the web and android version.
What’s good, is that I managed to figure out a way to share most of the code. This is what I learned sharing code between React.js and React Native.

Project Structure

I’ve been using [Create-React-App]. I wanted to combine a CRA folder structure, with what you get from react-native init.

Step 1 – Copy Public and Src

Create-React-App makes this simple as it relies mostly on react-scripts. The scripts rely on the source code being in a folder called src and the html file living in public.

So I copied those two folders from the CRA folder into the React Native folder. Then moved onto updating package.json.

Step 2 – Package.json

Nothing special here either. I just compared the two package.json files and pretty much merged them. Unfortunately, they both have a “start” task. So I prefixed all the CRA tasks with “web-” (lined 8-11 below).

{
  "name": "BasicPairs",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
    "web-start": "react-scripts start",
    "web-build": "react-scripts build",
    "web-test": "react-scripts test --env=jsdom",
    "web-eject": "react-scripts eject"
  },
  "dependencies": {
    "react": "15.4.1",
    "react-dom": "15.4.1",
    "react-native": "0.38.0",
    "react-native-button": "^1.7.1",
  },
  "jest": {
    "preset": "react-native"
  },
  "devDependencies": {
    "flow-bin": "^0.33.0",
    "babel-jest": "17.0.2",
    "babel-preset-react-native": "1.9.0",
    "jest": "17.0.3",
    "react-scripts": "0.7.0",
    "react-test-renderer": "15.4.1"
  }
}

The CRA site now runs with npm run web-start. The React Native app continues to run with react-native run-android.

So far, so good, but how to share code?

Sharing Code

In an ideal world, you would be able to share 100% of the code. As far as I know, that isn’t possible, but we can share a lot of it. Although React Native is like React.js, the rendering code is different. Instead of “div” you have “View”, text also has to be wrapped in “Text” tags and the list goes on. The “business logic” is just JavaScript though and that’s what we can share.

I don’t quite understand how this works. But as far as I can tell, it’s webpack magic. When webpack links the files, it takes notice of the filenames. The “trick”, is that you can have a React component defined in one file. Yet have the render method in a target platform specific file.

Webpack Linking Correct Files

Webpack Linking Correct Files

Let’s say you want to create a component called “MyComponent”. Webpack is aware of the target platform. Webpack uses this when trying to resolve `import MyComponent from ‘./MyComponent’:

  • When the target is android, it will search first for “MyComponent.Android.js”
  • If it can’t find that file and the target is android, it will search for “MyComponent.Native.js”
  • If it can’t find that file and the target is android or the target is web, it will search for “MyComponent.js”

What’s more, you can use this trick to split out just the render method. So let’s use that to our advantage to create a simple component.

Split Out the Render Method

To try it out, create a folder in src called MyComponent. Then create a “Render.js” file containing the following. This will be used in the CRA build.

import React from 'react';

export default function render() {
    return (
        <div>
            Hello World!
        </div >
    );
}

Then create a React Native file called “Render.native.js”. (this would be for Android and IOS):

import React from 'react';
import {
    Text,
    View,
    Image,
    StyleSheet,
    TouchableWithoutFeedback
} from 'react-native';

function render() {

    var styles = StyleSheet.create({
/…
    })

    return (
        <View style={styles.whatever}>
            <Text>
                Hello World
            </Text>
        </View>
    );
}

module.exports = render;

Finally, lets define the component. Create a file called “Component.js”:

import { Component } from 'react';
import Render from './Render';

class MyComponent extends Component {

    // Shared Code Here

    render() {
        return Render.bind(this)();
    }
}

export default MyComponent;

Note how Render is imported and then called in the render() method.

Any code defined here is available to both the “Render.js” and “Render.native.js” files.

That means you can define a single “onHandleClick” method and pass it down via props.

Warning

I’ve only been using this technique for a couple of weeks, so there may be problems with it.

My first attempt copied the folder structure from the React Native output into the CRA output. All was well until I tried a react-native upgrade. I’m not sure, but it appears that the folder name is used during upgrade. Long story short, it all stopped working and I only fixed it by starting again using this approach!

I might end up moving the src folder to a different package and using npm link to get it into 2 code bases. The code will be shared, but I’ll have two projects to maintain.

Just make sure you have everything under source control so you can rollback.

Conclusion

I’ve used this technique in Basic Pairs and have shared the vast majority of the code between the CRA and React Native app.

I added a button to the React Native app and plumbed in some onPress logic. Using this technique saved a lot of time as it worked first time for the CRA onClick event.

I’m always looking to improve. So please let me know in the comments below if I’ve made a mistake or missed something. Failing that, contact me on twitter.

2 Responses to “Sharing Code between React.js and React Native”

  1. Andrea says:

    Hi!

    I am doing some reserach before starting to develop a react-native app that will hopefully be able to share as much code as possible with an already exsisting rect app. I am wondering if you encountered any more problems so far when trying to make a shared code base for web and andrioid ??

    • Not really, but then I haven’t been doing anything too complex. I had a few issues with ‘this’, but nothing a bind() wouldn’t solve. I think the biggest problem I had was trying to put a react only (not native) library in the compenent.js file. When building for mobile it fell apart. As I wasn’t doing anything too complex I just rolled my own solution.

      I’d be really interested to hear how you get on, so please keep me posted.

Leave a Reply

Your email address will not be published. Required fields are marked *