Write once, run anywhere with Create React (Native) App and react-native-web
aka when the tools exist β¦ compose them!
EDIT January 2018: Iβve updated this article with up to date versions (no more beta dependencies!). Thanks @KajiTetsushi for your help! π
EDIT Feb 2018 : All the versions are up to date to go along with React v16.2.0. (No need to downgrade React to 16.0.0 anymore)
Since the first release of React Native, Iβve always hoped to build a React codebase that could run on any platform.
I loved building multi platform apps using Cordova and web technologies, but React Native has raised the bar so high now with its native performance.
So far, when you wanted to have a unified codebase of the Web and Native, you had to mess around with build systems (Webpack and React Native packager) in order to make everything work together.
Now with Create React Native App and Create React App, we do not have to worry about this layer of complexity.
Iβll guide you through the steps needed to have one codebase that runs anywhere!
Phase 1 : The dirty work
Youβll need :
- the latest version of Node (when I wrote this post, it was 8.6.0)
- create-react-app
First, create a React app using :
create-react-app my-hybrid-app && cd my-hybrid-app
We need to add a few dependencies to our project :
To install these packages you can either use npm
or yarn
.
npm install --save-dev babel-plugin-transform-object-rest-spread babel-plugin-transform-react-jsx-source babel-preset-expo jest-expo flow-bin react-native-scripts react-test-renderer@16.2.0oryarn add --dev babel-plugin-transform-object-rest-spread babel-plugin-transform-react-jsx-source babel-preset-expo jest-expo flow-bin react-native-scripts react-test-renderer@16.2.0
And we add the packages for react-native, react-native-web and expo :
npm install --save expo@^25.0.0 react-native@0.52.0 react-native-weboryarn add expo@^25.0.0 react-native@0.52.0 react-native-web
Now letβs add a few files necessary to build a React Native app :
- .babelrc
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-object-rest-spread", "transform-react-jsx-source"]
}
}
}
- .watchmanconfig
{}
- .flowconfig
Create a .flowconfig file in your project directory and add the flow settings available here
- app.json
{
"expo": {
"sdkVersion": "25.0.0"
}
}
App.test.js : this is the entry point for testing the React Native app.
import React from 'react';
import App from './App';import renderer from 'react-test-renderer';it('renders without crashing', () => {
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});
And then add a App.js file, this is going to be our React Native app entry point.
import React from 'react'
import HybridApp from './src/App'export default class NativeApp extends React.Component {
render() {
return <HybridApp />
}
}
At that point, your project should look like this :
my-hybrid-project
|
βββ README.md
βββ .babelrc
βββ .flowconfig
βββ .gitignore
βββ .watchmanconfig
βββ package.json
βββ app.json
βββ App.test.js
βββ App.js <-- entry point for CRNA (don't move/rename it!)
β
βββ src
β βββ index.js <- entry point for CRA (don't move/rename it)
β βββ ... more source files
β
βββ public
βββ index.html
βββ favicon.icon
βββ manifest.json
Now letβs make a few changes to our package.json file and add some useful scripts
// These scripts are merely copied from the create-react-native-app package.json file
...
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start-web": "react-scripts start",
"build-web": "react-scripts build",
"test-web": "react-scripts test --env=jsdom",
"eject-web": "react-scripts eject", "start-native": "react-native-scripts start",
"eject-native": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test-native": "node node_modules/jest/bin/jest.js --watch", "test": "npm run test-web && npm run test-native"
},
"jest": {
"preset": "jest-expo"
}
...
Phase 2 : Profit!
Everything is set up in our project, letβs start both packagers in two different terminal windows.
Terminal 1 :
npm run start-weboryarn start-web
Terminal 2 :
npm run iosor yarn ios
We need to edit ./src/App.js
and add some platform agnostic code in it.
You can now write all your code as if you were writing some React Native code.
The trick here is that the webpack config of Create React App aliases automatically react-native
to react-native-web
(see the config here).
Everything is handled for us!
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'export default class App extends Component {
render() {
return (
<View style={styles.app}>
<View style={styles.appHeader}>
<Text style={styles.appTitle}>Welcome to React βοΈ</Text>
</View>
<Text style={styles.appIntro}>
To get started, edit src/App.js and save to reload.
</Text>
</View>
)
}
}const styles = StyleSheet.create({
app: {
flex: 1
},
appHeader: {
flex: 1,
backgroundColor: '#222',
padding: 20,
justifyContent: 'center',
alignItems: 'center'
},
appTitle: {
fontSize: 16,
color: 'white'
},
appIntro: {
flex: 2,
fontSize: 30,
textAlign: 'center'
}
})
You should now see this :
The result on the web is not really the same as on the mobile. Thatβs because the #root
DOM element styling needs to be tweaked to match its mobile counterpart :
To do so, open ./src/index.css
and add
/* ... */#root {
display: flex;
flex-direction: column;
min-height: 100vh;
max-height: 100vh;
}
And Tada!
Youβve built your first universal React component running on the Web, iOS and Android.
I did not invent anything here : I leveraged the amazing work done by the teams behind Create React App, Create React Native App, react-native-web and Expo.
You should check these projects out and support them! They help making React development a accessible to everyone!
Here is an awesome starter repo by @PureFazz to get started with this setup very quickly! π
Interested into more platform agnostic React ? π
Check out these answers where I explain :
Originally published on sparkyspace.com