Tell us about your project

Foggy pic of red and blue element converging

Sharing code between React JS and React Native is an interesting idea. Writing in one language and
running it both on a Web and as a native App. But because React Native doesn't use HTML to render the
App and provides special components, it can't be done just out of the box.

After hours of research, I`ve found a way to reuse most of the code. In other words, React Native has a platform, a specific way of loading modules. In official documentation under Platform-specific extensions, it's not written that React Native detects .native extensions that are used for both iOS and Android. Knowing that I will use it to my advantage.

Example app

In this article, I will create a simple App that will not have any logic, just a Title and a Lorem Ipsum text that will run both as a Web App and an Android/iOS native App. I will start by generating a bare React Native App with:

$ react-native init ReactCodeReuse
$ cd ReactCodeReuse/

Once we create the index.web.js file in ReactCodeReuse/ folder X, that will be the entry point for the Web App ( the public/ directory which will be our web folder and the app/ folder ). The main App code will be in the newly created app/ folder, there we'll normally put all our components, reducers, actions, etc.

The next thing is to install all dependencies. For that I will use the YARN package manager:

$ yarn add react-dom babel-loader babel-core gulp gulp-webserver webpack webpack-stream

I won't get into what an individual package is, though, through the article we'll talk about some of the packages and what they are used for.

Now that the dependencies are installed we can proceed with setting up the Web part, so when we begin to code the App components, we'll see them in the browser. We'll start with creating webpack.config.js, a config file for the Webpack package manager, which is an open-source JavaScript module bundler. Webpack takes modules with dependencies and generates static assets representing those modules. In other words, it will take all our code with all its dependencies like the ones we installed and pack them in a single .js file that is going to be used in our Web App as all the logic for displaying, functionality, etc

module.exports = {
    entry: "./index.web.js",
    output: {
        filename: "public/js/script.js",
        sourceMapFilename: "public/js/script.map"
    },
    devtool: '#source-map',
    module: {
        loaders: [
            {
                loader: 'babel-loader',
                exclude: /node_modules/
            },
        ]
    }
};

In the code above I have specified what's our App's entry point (index.web.js), where output is to be created (in folder public/js), and also the name of the output .js file (script.js.). We are using babel-loader because this package allows transpiling JavaScript files using Babel and Webpack.

With that done, we must now create gulpfile.js which is a config file for gulp.js. Gulp is a task execution tool based on the Node.js and the Node Package Manager (npm) package management system. It is used to automate tasks that are repetitive and time-consuming.

const gulp = require('gulp');
const webserver = require('gulp-webserver');
const del = require('del');
const webpack = require('webpack-stream');
const webpackConfig = require('./webpack.config.js');

gulp.task('clean:build', function() {
    del('./public/js/*');
});

gulp.task('build', ['clean:build'], function() {
    gulp.src('./index.web.js')
        .pipe(webpack(webpackConfig))
        .on('error', function handleError() {
            this.emit('end'); // Recover from errors
        })
        .pipe(gulp.dest('./'));
});

gulp.task('watch', function() {
    gulp.watch(['./app/**/**/**/*', './public/css/**/*', './index.web.js'], ['build']);
});

gulp.task('serve', ['watch'], function() {
    gulp.src('public')
        .pipe(webserver({
            fallback: 'index.html',
            livereload: true,
            directoryListing: false,
            open: true
        }));
});

The above code explains that first we loaded all necessary modules, and then we created the tasks that would help us in development.

The task "clean:build" deletes our public/js/ folder, where our script.js ( the “brain” of our Web App ) is residing. The task "build" starts by calling the "clean:build" task, then initiates the building process of our script.js file.
The "watch" task continuously looks if there are any changes in all subfolders of our app folder, changes on index.web.js, and changes in the public/css/ folder. If there are some changes, it calls the "build" task to rebuild the script.js file.

The "serve" task first calls the "watch" task, and starts the Web server in our public/ folder. The Web server's starting point is the Index.html file. 'Livereload' is true, so we don't have to reload the App every time we make changes.

In the public/ folder we need to create the index.html file and a css/ folder with a style.css file within. Index.html is going to be a simple .html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React Code Reuse</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div id="root"></div>
    <script src="js/script.js"></script>
</body>
</html>

The important parts of this .html are:

  • the script tag with a call of our script.js which is the bundled App;
  • style.js file which is going to be our stylesheet for the App, and
  • div with id=”root”.

For good measure, we added bootstrap CSS as well.

Now that the configuration is out of the way, we can proceed with App creation. First, we create the folder components/ in the app/ folder. Inside the components/ folder, we create the folder appscreen/. This is where we are going to put our DOM code for Web and native versions. Return to the root folder of the App and populate the index.web.js:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/appscreen';
ReactDOM.render(<App />, document.getElementById('root'));

Here we`ve created an entry point for our Web App by ReactJS standards. Here we are importing the App component which is our root component of the App, within this element all the other components, functionality, etc are contained and this all will be loaded in the element with id=”root”, which is our div inside public/index.html. We must do the same thing for the native App entry point, but by React Native standard, so in index.js file:

import { AppRegistry } from 'react-native';
import App from './app/components/appscreen';
AppRegistry.registerComponent('ReactCodeReuse', () => App).

When in import component like we did index.web.js and in index.js

import App from './app/components/appscreen';

import React from 'react';
import screenView from './screenView';
export default class screenContainer extends React.Component {
  render() {
    return (
      <screenView/>
     )
  }
}

In this code snippet, we are calling the screenView file from the same folder as index.js. Previous knowledge that React Native is calling the file with a .native in its name helps us. When that component comes up for rendering, it will be from file screenView.native.js, and when the same component comes up for rendering in Web it will call screenView.js because React JS doesn't recognize .native in the name.

We will now populate screenViews files screenView.native.js

import React, { Component } from 'react';
export default class screen extends React.Component {
  render() {
    return (
        <div className="container">
            <div>
                <p className="title">header Text</p>
            </div>
            <div>
                <p>
                    Lorem Ipsum …
               </p>
            </div>
        </div>
    );
  }
}
screenView.native.js
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
 
export default class Screen extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <View>
                    <Text style={styles.title}>header Text</Text>
                </View>
                <View>
                    <Text>
                        Lorem Ipsum …
                    </Text>
                </View>
            </View>
        )
    }
}
 
const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        backgroundColor: '#add8e6',
     },
    title: {
        fontSize: 32,
        textAlign: 'center'
        color: '#FFFFFF',
        fontWeight: 'bold',
        marginBottom: 20
    },
})

The last thing we must do is to apply those styles to the Web App, so in public/css/style.css add some CSS for styling the view.

Public/css/style.css

body,
html{
    height: 100%;
    width: 100%;
}
#root{
    height: 100%;
}
.container{
    height: 100%;
    vertical-align: middle;
    text-align: center;
    background: #add8e6;
}
.title{
    font-size: 32px;
    text-align: center;
    color: #FFFFFF;
    font-weight: bold;
    margin-bottom: 20px;
}

With coding out of the way the only thing left is to start both Apps and we are done.

React js: gulp serve
React Native: react-native run-android / run-ios

 

Conclusion

This is just the beginning of what this approach can accomplish. In the next article, I will move on to adding logic to this App and add Redux to the mix. This is where the true power of this approach will be seen - we'll create one logic and use it in both Web and the Native App.

 

Similar content can be found on: Sharing Code between React and React Native Apps part 2

 


Boris Žmaher