React Native Web
Comprehensive Guide to create simple app using React Native Web and React Native Elements
Case study: gitphone
, GitHub repository checker for your smartphone.
#
ToC- Initialize the project using CRNWA
- Make sure everything works: Android, iOS and Web
- Add RNE latest ( 1.0.0-beta7 ) to the project
- Add 2 screens
Screen 1 RNE components:
- Input
- Button with Icon
Screen 2 RNE components:
- Header
- ListItem
- Avatar
- Text
gitphone
should have:
- routing, react-router. It works great both on Native and Web.
- calls to GitHub API. See https://developer.github.com/v3/.
#
0️⃣ Initial StepInstall create-react-native-web-app
$ npm i -g create-react-native-web-app
#
1️⃣ First StepCreate gitphone
project.
λ create-react-native-web-app gitphone
⏳ Creating React Native Web App by the name of gitphone ...
✅ Created project folder.
✅ Added project files.
⏳ Installing project dependencies...
yarn install v1.10.1[1/4] Resolving packages...[2/4] Fetching packages...info fsevents@1.2.4: The platform "win32" is incompatible with this module.info "fsevents@1.2.4" is an optional dependency and failed compatibility check. Excluding it from installation.[3/4] Linking dependencies...[4/4] Building fresh packages...success Saved lockfile.Done in 797.66s.
✅ Installed project dependencies.
✅ Done! 😁👍 Your project is ready for development.
* change directory to your new project$ cd gitphone
$ Then run the these commands to get started:
* To run development Web server$ yarn web
* To run Android on connected device (after installing Android Debug Bridge "adb" - https://developer.android.com/studio/releases/platform-tools)$ yarn android
* To run ios simulator (after installing Xcode - only on Apple devices)$ yarn ios
* To run tests for Native and Web$ yarn test
* To run build for Web$ yarn build
Change to gitphone
directory and test the web app by running yarn web
.
Starting the development server...Compiled successfully!You can now view create-react-native-web-app in the browser.Local: http://localhost:3001/On Your Network: http://172.26.235.145:3001/Note that the development build is not optimized.To create a production build, use yarn build.
Now, test the gitphone
android app by running yarn android
.
Installing APK 'app-debug.apk' on 'Redmi 4X - 7.1.2' for app:debugInstalled on 1 device.
BUILD SUCCESSFUL
Total time: 21.783 secs
Starting: Intent { cmp=com.creaternwapp/.MainActivity }✨ Done in 25.64s.
If the build successful, you'll see the app installed on your Android (emulator) device.
But if you got an error when run yarn android
, please see Troubleshooting section below.
The last part for First Step, make sure it can be run on iOS without any problem. Run yarn ios
and voila!
#
2️⃣ Step TwoInstalling React Native Elements (RNE).
$ yarn add react-native-elements@beta
Installing React Native Vector Icons (RNVI).
$ yarn add react-native-vector-icons
Linking:
$ react-native link react-native-vector-icons
Both RNE and RNVI are written using es6. If you run yarn web
at this point, you'll got an error.
./node_modules/react-native-elements/src/config/withTheme.jsModule parse failed: Unexpected token (12:28)You may need an appropriate loader to handle this file type.
We need to tell webpack to transpile them.
- Open
config/webpack.config.dev.js
- On line 141
Process JS with babel
, add RNE and RNVI to include - Do the same for
config/webpack.config.prod.js
as well 👌
If you get lost, see this gist or commit 8c0e603.
#
3️⃣ Give it a tryNow, let's grasp the idea how RNE works.
Open src/App.js
Import Button from RNE
import { Button } from 'react-native-elements';
On render, change TouchableHighlight to use RNE's Button
Run yarn ios, yarn android and yarn web to see it in action! 👏
Note: If you got an error Could not find com.android.tools.build.appt2
when running yarn android
, add google
on the gradle repositories.
See this gist or commit for the details: a2ebba1.
#
4️⃣ Add Home componentOur first component will be Home
. On this component, there are two input fields and one Submit button.
- Inside
src
, create new folder:Components
- Add new file called
Home.js
gist - On
App.js
, importHome
component gist - Run
yarn ios
,yarn android
andyarn web
to see it in action! 🎇
#
Styling for Home componentYou should notice that our Home
doesn’t look good in term of UI. Let’s add styling for it.
- Inside
Components
, createShared.style.js
file gist - Import the style and update
Home
component as below gist - Looks better now*, commit for adding Home component: 2e510c4.
Wait a minute… *Seems there is a problem with RNVI on the web version. You can check this Web (with webpack) article or just following steps bellow.
- Open
config/webpack.config.dev.js
- Add url-loader on line 162 gist
- Do the same for
config/webpack.config.prod.js
as well 👌 - Open
src/index.js
file - Add
iconFont
and appendstyle
to document’s head gist
Our RNE x RNW progress so far~
#
5️⃣ RoutingNext, let’s add second component: CommitList
.
- Create new folder inside
Components
namedCommit
- Add new file:
CommitList.js
gist
On our app, user goes to second screen by click on Submit
button. How do we implement it?
“react-router comes to the rescue” - https://reacttraining.com/react-router/
Add react-router-dom and react-router-native
$ yarn add react-router-dom react-router-native
Web needs BrowserRouter
while native NativeRouter
. We need to separate it based on the platform.
- On
src
, createUtils
folder - Add two files on
Utils
:Routing.native.js
andRouting.web.js
gist
Those file’s content differ only on the second line. gist
Now, glue it together.
Open
App.js
, importCommitList
componentImport
Route
,Router
andSwitch
fromUtils/Routing
Implement routing inside
render
method gistNow for the action on
Submit
button, openHome.js
Import
withRouter
fromUtils/Routing
import { withRouter } from '../Utils/Routing';
WithRouter
is an HOC. Use it to wrapHome
componentexport default withRouter(Home);
Add
onPress
property for the buttononPress={this.onPressButton}
Implement the
onPressButton
event handleronPressButton = () => this.props.history.push('/commit');
Test it on web
and android
, you should be able to go back and forth between screens using Submit
and pressing Back
button.
“How can I go back on iOS?” 😂
#
Implement withHeaderWe will create a withHeader
HOC. Why HOC? We can reuse it easier if we add more screens later.
On
src
, createHOCs
folderAdd
withHeader.js
fileImport
Header
from RNE andIcon
fromRNVI/FontAwesome
import { Header } from 'react-native-elements';import Icon from 'react-native-vector-icons/FontAwesome';
withHeader
accepts one prop:title
const withHeader = ({ title = '' }) => (WrappedComponent) => {
Event handler to go back / go home
goBack = () => this.props.history.goBack(); goHome = () => this.props.history.replace('/');
Import and use
withHeader
inCommitList
component gist | commit
#
6️⃣ Fetch data from GitHub APILet’s fetch a real-live data: list commit on repository by GitHub and render it on our second screen, CommitList
.
GET /repos/:owner/:repo/commits
Ideally, the :owner and :repo are form values from our first screen. Since the objective of this article is RNE x RNW, talk about that form (and state-management) later on.
To fetch GitHub API, we will use fetch-hoc package and also need compose from redux
, to handle multiple HOCs on the same component.
$ yarn add fetch-hoc redux
Open
CommitList.js
Import
{ compose }
fromredux
andfetch
fromfetch-hoc
Now run yarn web
, open network
tab of DevTools
and click Submit
button, you’ll see bunch of commit data. By default GitHub API returning 30 commits.
#
Render commit dataCommit data that will be displayed on the screen:
author.avatar_url
commit: author.name message
Let’s modify CommitList.js
Add new imports
import { ActivityIndicator, Dimensions, FlatList, Platform, View } from 'react-native';import { Avatar, ListItem } from 'react-native-elements';
On main render, modify it as below
<View style={styles.container}> {this.renderContent()}</View>
Create
renderContent
methodrenderContent = () => ( this.props.loading ? <ActivityIndicator color='#87ceeb' /> : <FlatList keyExtractor={this.keyExtractor} data={this.props.data} renderItem={this.renderItem} />)
Create
renderItem
methodrenderItem = ({ item }) => ( <ListItem title={item.commit.author.name} subtitle={item.commit.message} leftElement={this.renderLeftElement(item) />)
Create
renderLeftElement
methodrenderLeftElement = (item) => ( <View> <Avatar source={{ uri: item.author.avatar_url }} size='medium' rounded /> </View>)
Here is our new
CommitList
including the styling to make it prettier gist | commit
Here they are!
awesome, eh?
#
7️⃣ Handle form submissionOur app looks great so far. But we are not passing values from first to second screen. Let’s do it.
To handle form, we’ll use formik
$ yarn add formik
Open
Home.js
and import itimport { Formik } from 'formik';
Wrap main
View
withformik
<Formik initialValues={{ owner: '', repo: '' }} onSubmit={this.onPressButton}> {({ handleChange, handleSubmit, values }) => ( <View style={styles.container}>
Add
onChangeText
handler to theInput
<Input ... onChangeText={handleChange('owner')} value={values.owner}<Input ... onChangeText={handleChange('repo')} value={values.repo}
Change
Button
onPress
props tohandleSubmit
<Button ... onPress={handleSubmit}
Don’t forget to close the main
View
</View> )}</Formik>
Form submission: done 👌 Next question: How do we pass these values to second screen? Send them when we redirect to second screen!
Inside
onPressButton
method, send an object instead ofpathname
only.this.props.history.push({ pathname: '/commit', state: { owner, repo }});
Open
CommitList
, importwithRouter
import { withRouter } from '../../Utils/Routing';
Add
withRouter
insidecompose
Get the values passed down to
withRouter
and use it tofetch
withHeader({ title: 'Commits' }),withRouter,fetch(({ location: { state = {} } }) => ( `https://api.github.com/repos/${state.owner}/${state.repo}/commits`))
HOC’s order does matter. So, make sure it the same as snippet above. In case you lost, here is the commit: 1d83c5e.
Test the app. Now we should able to fetch any GitHub repository, with some caveats. 👀
#
8️⃣ Polishing the appWhat happens if we fetch repository which doesn’t exist? Red screen on native, blank screen on web! 😹
fetch-hoc
returns an error if it has. Let’s use it.
On
CommitList
, modifyrenderContent
this.props.loading ? <ActivityIndicator color='#87ceeb' /> : this.renderFlatList()
Import
Text
from RNEimport { ..., Text } from 'react-native-elements';
Add
renderFlatList
methodthis.props.error ? <Text h4>Error: {this.props.data.message || '😕'}</Text> : <FlatList ... />
Test it. Instead of red or blank screen, now Error: Not Found
displayed.
What’s else? Try to fetch facebook/react-native
. We got another error 🙀
Cannot read property 'avatar_url' of null
Not all of author
have avatar_url
. We should do this for the Avatar source
.
source={{uri: (item.author && item.author.avatar_url) || undefined}}
So, our app renders nothing if it has no url? It doesn’t look good. Solution: render author initial name.
With the help of RegEx and Avatar title
props, renderLeftElement
should look like this now:
renderLeftElement = (item) => { const initials = item.commit.author.name.match(/\b\w/g) || [];
return ( <View style={leftElementStyle}> <Avatar title={((initials.shift() || '') + (initials.pop() || ''))} ...
Commit for Polishing the app section: 943974b.
When I wrote this, fetch facebook/react-native
returning following:
Why no love for regex? Thanks to Sanoor.
#
ConclusionWe have created a simple app using RNE + RNW 👏
Works great on iOS, web and android? ✅
Use components from react-native-elements? ✅
Move between screens? ✅
API calls? ✅
Some improvements for gitphone
:
If you go back from Commits
screen, input form on Home
screen are empty. If you want preserve previous values, this can be fixed easily by introducing redux to the app. References here: 48108dd.
Can we fetch more commits data once we reach the most bottom of the list? Infinite scroll?
For web, we can use react-visibility-sensor. Check it out: 6c1f689.
For native, it’s easier. We can use FlatList
onEndReached
props. To give you an idea how, see this: 9d2e1f2.
#
Troubleshooting 💺#1 Build failed when running yarn android
:app:compileDebugAidl FAILED
FAILURE: Build failed with an exception.
* What went wrong:Execution failed for task ':app:compileDebugAidl'.> java.lang.IllegalStateException: aidl is missing
* Try:Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Here is how to fix #1:
- Open Android Studio.
- Open
android
project undergitphone
.
- Click Update on this prompt.
Wait for Android Studio syncing the project.
- It synced successfully with two errors.
At this stage, just click
Update Build Tools version and sync project
on the sync window.Now, the remaining warning is the
Configuration 'compile'...
To fix that, open
app/build.gradle
file, changedependencies
section (line 139) to useimplementation
instead ofcompile
.dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:23.0.1" implementation "com.facebook.react:react-native:+" }
Sync it again and close Android Studio.
Troubleshooting for android is done. Now, you should be able to run yarn android
successfully.
#2 Build failed when running yarn ios
** BUILD FAILED **
The following build commands failed:
CompileC /gitphone/ios/build/Build/Intermediates.noindex/React.build/Debug-iphonesimulator/double-conversion.build/Objects-normal/x86_64/strtod.o /gitphone/node_modules/react-native/third-party/double-conversion-1.1.5/src/strtod.cc normal x86_64 c++ com.apple.compilers.llvm.clang.1_0.compiler
Here is how to fix #2:
Inside the project, run script below from your favourite terminal
$ curl -L https://git.io/fix-rn-xcode10 | bash
If you run yarn ios
again, and you got this error
The following build commands failed: Libtool /gitphone/ios/build/Build/Products/Debug-iphonesimulator/libRCTWebSocket.a normal x86_64(1 failure)
Please run this script:
$ cp ios/build/Build/Products/Debug-iphonesimulator/libfishhook.a node_modules/react-native/Libraries/WebSocket
Troubleshooting for iOS is done. Now, you should be able to run yarn ios
successfully.