> 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:

1. Input
2. Button with Icon

Screen 2 RNE components:

1. Header
2. ListItem
3. Avatar
4. 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 Step

Install create-react-native-web-app

    $ npm i -g create-react-native-web-app

##
1️⃣ First Step

Create `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:debug
    Installed 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 Two

Installing 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.js
    Module 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.

1. Open `config/webpack.config.dev.js`
2. On line 141 `Process JS with babel`, add RNE and RNVI to include
3. 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 try

Now, let's grasp the idea how RNE works.

1. Open src/App.js

2. Import Button from RNE

       import { Button } from 'react-native-elements';

3. On render, change TouchableHighlight to use RNE's Button

4. 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 component

Our first component will be `Home`. On this component, there are two input fields and one Submit button.

1. Inside `src`, create new folder: `Components`
2. Add new file called `Home.js` gist
3. On `App.js`, import `Home` component gist
4. Run `yarn ios`, `yarn android` and `yarn web` to see it in action! 🎇

###
Styling for Home component

You should notice that our `Home` doesn’t look good in term of UI. Let’s add styling for it.

1. Inside `Components`, create `Shared.style.js` file gist
2. Import the style and update `Home` component as below gist
3. 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.

1. Open `config/webpack.config.dev.js`
2. Add url-loader on line 162 gist
3. Do the same for `config/webpack.config.prod.js` as well 👌
4. Open `src/index.js` file
5. Add `iconFont` and append `style` to document’s head gist

Our RNE x RNW progress so far~

##
5️⃣ Routing

Next, let’s add second component: `CommitList`.

1. Create new folder inside `Components` named `Commit`
2. 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.

1. On `src`, create `Utils` folder
2. Add two files on `Utils`: `Routing.native.js` and `Routing.web.js` gist

Those file’s content differ only on the second line. gist

Now, glue it together.

1. Open `App.js`, import `CommitList` component

2. Import `Route`, `Router` and `Switch` from `Utils/Routing`

3. Implement routing inside `render` method gist

4. Now for the action on `Submit` button, open `Home.js`

5. Import `withRouter` from `Utils/Routing`

       import { withRouter } from '../Utils/Routing';

6. `WithRouter` is an HOC. Use it to wrap `Home` component

       export default withRouter(Home);

7. Add `onPress` property for the button

       onPress={this.onPressButton}

8. Implement the `onPressButton` event handler

       onPressButton = () => this.props.history.push('/commit');

9. All `Home` together gist | 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 withHeader

We will create a `withHeader` HOC. Why HOC? We can reuse it easier if we add more screens later.

1. On `src`, create `HOCs` folder

2. Add `withHeader.js` file

3. Import `Header` from RNE and `Icon` from `RNVI/FontAwesome`

       import { Header } from 'react-native-elements';
       import Icon from 'react-native-vector-icons/FontAwesome';

4. `withHeader` accepts one prop: `title`

       const withHeader = ({ title = '' }) => (WrappedComponent) => {

5. Event handler to go back / go home

       goBack = () => this.props.history.goBack();

       goHome = () => this.props.history.replace('/');

6. Import and use `withHeader` in `CommitList` component gist | commit

##
6️⃣ Fetch data from GitHub API

Let’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

1. Open `CommitList.js`

2. Import `{ compose }` from `redux` and `fetch` from `fetch-hoc`

3. Use it as below gist | commit

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 data

Commit data that will be displayed on the screen:

    author.avatar_url

    commit:
      author.name
      message

Let’s modify `CommitList.js`

1. Add new imports

       import { ActivityIndicator, Dimensions, FlatList, Platform, View } from 'react-native';
       import { Avatar, ListItem } from 'react-native-elements';

2. On main render, modify it as below

       <View style={styles.container}>
         {this.renderContent()}
       </View>

3. Create `renderContent` method

       renderContent = () => (
         this.props.loading ?
           <ActivityIndicator color='#87ceeb' /> :
           <FlatList
             keyExtractor={this.keyExtractor}
             data={this.props.data}
             renderItem={this.renderItem}
           />
       )

4. Create `renderItem` method

       renderItem = ({ item }) => (
         <ListItem
           title={item.commit.author.name}
           subtitle={item.commit.message}
           leftElement={this.renderLeftElement(item)
         />
       )

5. Create `renderLeftElement` method

       renderLeftElement = (item) => (
         <View>
           <Avatar
             source={{ uri: item.author.avatar_url }}
             size='medium'
             rounded
           />
         </View>
       )

6. Here is our new `CommitList` including the styling to make it prettier gist | commit

Here they are!

awesome, eh?

##
7️⃣ Handle form submission

Our app looks great so far. But we are not passing values from first to second screen. Let’s do it.

1. To handle form, we’ll use formik

       $ yarn add formik

2. Open `Home.js` and import it

       import { Formik } from 'formik';

3. Wrap main `View` with `formik`

       <Formik initialValues={{ owner: '', repo: '' }}
         onSubmit={this.onPressButton}>
         {({ handleChange, handleSubmit, values }) => (
           <View style={styles.container}>

4. Add `onChangeText` handler to the `Input`

       <Input ... onChangeText={handleChange('owner')} value={values.owner}
       <Input ... onChangeText={handleChange('repo')} value={values.repo}

5. Change `Button` `onPress` props to `handleSubmit`

       <Button ... onPress={handleSubmit}

6. 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!

1. Inside `onPressButton` method, send an object instead of `pathname` only.

       this.props.history.push({
         pathname: '/commit',
         state: { owner, repo }
       });

2. Open `CommitList`, import `withRouter`

       import { withRouter } from '../../Utils/Routing';

3. Add `withRouter` inside `compose`

4. Get the values passed down to `withRouter` and use it to `fetch`

       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 app

What 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.

1. On `CommitList`, modify `renderContent`

       this.props.loading ?
         <ActivityIndicator color='#87ceeb' /> :
         this.renderFlatList()

2. Import `Text` from RNE

       import { ..., Text } from 'react-native-elements';

3. Add `renderFlatList` method

       this.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.

##
Conclusion

We 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:

1. Open Android Studio.

2. Open `android` project under `gitphone`.

3. Click Update on this prompt.

Wait for Android Studio syncing the project.

4. It synced successfully with two errors.

5. At this stage, just click `Update Build Tools version and sync project` on the sync window.

6. Now, the remaining warning is the `Configuration 'compile'...`

7. To fix that, open `app/build.gradle` file, change `dependencies` section (line 139) to use `implementation` instead of `compile`.

   dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:23.0.1" implementation "com.facebook.react:react-native:+" }

8. 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.

###
Authors

- haruelrovix
- andangrd

Recent Posts