Skip to content

Sharing of React code across Web, iOS, Android, Electron as Hello World example

License

Notifications You must be signed in to change notification settings

gfogle/multi-platform-react

Repository files navigation

multi-platform-react

Sharing of React code across Web, iOS, Android, Electron as Hello World example. Before you read on, just a quick shout out that there are a lot of web resources I used to build this out; I'm not a savant. I'll go back through my browser history at some point, its a scary place to be, and add shout outs.

FOLDER STRUCTURE

React Native init

Repo was created using react-native init MultiPlatformReact which initially created a folder named the same in the root project. In addition, this uses eslint so there was an addition npm install --save-dev eslint-plugin-react command run. The initial structure looked like:

Initial Project structure

Moving things around

To make the project a little easier to navigate (I hope). Ideally, the specific index files would be in their respective folders but there are issues where parts of the project expect them in the root, so they're all there for consistency. The files now reside at the following:

Initial Project structure

Shared Components

The goal of these components are that, to extent you can, keep react-native in the native files and react in the plain .js files. There's only a root component that imports a root-render function. The web / electron files pickup the standard root-render.js file. So each component built is going to have 5 files. The root component has:

- root-render.android.js
- root-render.ios.js
- root-render.js
- root-render.native.js
- root.js

The base root file will pull in root-render.js and looks like:

'use strict';

import Render from './root-render';

import { Component } from 'react';

export default class Root extends Component {
	render () {
		return Render.call(this, this.props, this.state);
	}
}

but the iOS and Android platforms will look for the specific root-render.ios.js and root-render.android.js files, based on the platform, and both of those look like:

'use strict';

import Render from './root-render.native';

export default function () {
	return Render.call(this, this.props, this.state);
}

and redirect to the root-render.native.js file and look like:

'use strict';

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

export default function () {
	return (
		<View style={styles.container}>
			<Text>Hello World</Text>
		</View>
	);
}

var styles = StyleSheet.create({
	container: {
		flex: 1,
		flexDirection: "column",
		justifyContent: "center",
		backgroundColor: "#303030"
	}
});

Web and ELECTRON

Each of the web and electron, which uses the web code, have their own file for the index.html and index.electron.js files. Webpack is the build system for web code and will output its bundle files to the web folder. In order to not cause issues with react native, we need to add an rn-cli.config.js file to blacklist the web folder for weird duplicate entry / dependency errors. Pretty simple:

var blacklist = require('react-native/packager/blacklist');

var config = {
	getBlacklistRE(platform) {
		return blacklist(platform, [
			/web/
		]);
	}
};

module.exports = config;

When trying to run the electron version, make sure to run the webpack build to write the bundle.js file to the folder for electron to pickup. The webpack dev server doesn't currently do that with this configuration.

The project uses webpack for bundling the web code and webpack-dev-server for basic hosting of the app locally. This is because the project I started this for is a static hosted app on Amazon S3 so I don't need a server in production. The config will be found by webpack automatically based on its name and looks like:

var path = require('path');
var webpack = require('webpack');

module.exports = {
	entry: './index.js',
	output: {
		path: './web',
		filename: 'bundle.js'
	},
	module: {
		loaders: [
			{
				test: /.js?$/,
				loader: 'babel',
				exclude: /node_modules/,
				query:
				{
					presets:['es2015','react']
				}
			}
		]
	},
	debug: true,
	devtool: "source-map",
	plugins: [
		new webpack.DefinePlugin({
			__DEV__: process.env.NODE_ENV === "development"
		})
	]
};

The webpack config is pretty basic but does inject a nice __DEV__ variable that you can use to hide things in development you don't quite want in prod such as, say in a component's render function you wanted to hide a tag:

export default function () {
	let forgotPassword;

	// this code will show up for you locally, but not when the webpack
	// production build kicks off
	if (__DEV__) {
		forgotPassword =
			<button
				style={styles.forgot}
			>Forgot Password?</button>;
	}

	return (
		<div>
			<h2>Hello World</h2>
			{forgotPassword}
		</div>
	);
}

And, the commands for all of this to work are in the package.json file and look like this:

"scripts": {
  "start:electron": "node_modules/.bin/electron electron/index.electron.js",
  "start:ios": "node_modules/react-native/packager/packager.sh",
  "start:web": "NODE_ENV=development node_modules/.bin/webpack-dev-server --progress --colors  --content-base web/",
  "start:android": "node_modules/react-native run-android",
  "build:web": "NODE_ENV=production node_modules/.bin/webpack -p",
  "build:ios": "react-native bundle --entry-file index.ios.js --bundle-output ios/main.jsbundle --platform ios --dev false",
  "build:android": "node_modules/react-native bundle --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --platform android --dev false"
}

At this point, the project should be working assuming you followed the Getting Started with React Native

At some point you'll probably want to add Redux because, well, the internet told you too (just kidding it's dope!) and there's a little gotcha to remember: as of writing this repo you have to use a specific version of the react-redux package. So, make sure you have "react-redux": "^3.1.2", in your package.json file and then also install these:

- redux
- redux-thunk

Then, to keep things simple I'd start with a folder called redux and have the following files in it:

- actions.js
- reducers.js
- constants.js
- store.js

The root component would then get updated to look something like:

import Render from './root-render';
import { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from "../../redux/actions";


class Root extends Component {
	constructor(props) {
		super(props);
	}
	render () {
		return Render.call(this, this.props, this.state);
	}
}

function mapStateToProps(state) {
	// add as many properties as you want mapped to props of root
	return {
		example: state.default.example,
	};
}

function mapDispatchToProps(dispatch) {
	return bindActionCreators(actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Root);

I'll leave the constants, actions, and reducers to you but these store file would look something like:

'use strict';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);

export default store;

And finally your index.js file would then wrap your root component with the react redux magic and look like this:

import React from 'react';
import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';
import store from './src/redux/store';
import Root from './src/components/root/root';

ReactDOM.render(
	<Provider store={store}>
		<Root />
	</Provider>,
	document.getElementById('root')
);

About

Sharing of React code across Web, iOS, Android, Electron as Hello World example

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published