Build Mobile-Friendly Web Apps with React Native Web

PostedDecember 12, 2019 15k views JavaScript

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Introduction

Over the years, building web applications that are mobile friendly has become easier with the advent of media queries and the introduction of service workers. Using media queries, we could make web applications that different shapes when viewed on mobile devices. Service workers, with their powerful features, present web applications with powers only native applications have been known to possess, like push notifications and background sync, among other features.

React Native is a multi-platform solution developed by Facebook that lets you build mobile apps using JavaScript. These mobile apps are considered multi-platform because they’re written once and deployed across many platforms, like Android, iOS and the web.

In this tutorial you’ll build an application that displays user information from the [Random User API](https://randomuser.me/ using React Native components like ScrollView, Text and Image. The app will run both on the web and mobile using the React Native Web library, which lets you use React Native components and APIs in web applications.

Prerequisites

To complete this tutorial, you’ll need:

Step 1 – Creating the Project

Before we get started, we’ll need to set up the project and install project dependencies. We’ll be making use of Create React App to bootstrap our application. We’re using Create React App because it can be configured to alias React Native. We’ll be installing polyfills for some of the latest JavaScript APIs like Promise, Array.from, etc., as the transpiler doesn’t provide those.

To bootstrap your application using Create React App, run the following command:

  • npx create-react-app random-people

Run the following command to install the project’s development dependencies. If you’re using Yarn, use this command:

  • yarn add --dev babel-plugin-module-resolver babel-plugin-transform-object-rest-spread babel-plugin-transform-react-jsx-source babel-preset-expo

If you’re using npm, use this command:

  • npm install --save-dev babel-plugin-module-resolver babel-plugin-transform-object-rest-spread babel-plugin-transform-react-jsx-source babel-preset-expo

The babel-plugin-module-resolver is a plugin that resolves your project modules when compiling with Babel. We’ll use this package to alias react-native to react-native-web when setting up the project config.

To build and run our application, we’ll be using Expo. Expo is an open-source toolchain built around React Native for building Android and iOS applications. It provides access to the system’s functionality like the Camera, Storage, etc.

Install the expo-cli module by running the following command if you’re using Yarn:

  • yarn global add expo-cli

Or, if using npm, use this command:

  • npm i g expo-cli

The next step is to install expo locally, alongside React Native and React Native Web. Run the command that follows to install the packages using Yarn:

  • yarn add expo react-native react-native-web react-art

Or, for npm:

  • npm i expo react-native react-native-web react-art

After downloading the packages needed to run and build the application, the next step is to set up the configuration files. Create a file called .babelrc in the root of your project:

  • nano .bablerc

Add the following code to the file to configure the transpilers your project will use:

.babelrc
{
  "presets": ["babel-preset-expo"],
  "env": {
    "development": {
      "plugins": ["transform-object-rest-spread", "transform-react-jsx-source"]
    }
  },
  "plugins": [
    [
      "module-resolver",
      {
        "alias": {
          "^react-native$": "react-native-web"
        }
      }
    ]
  ]
}

Create a file named app.json. This file is used to configure parts of your application that don’t belong in the code like the application name, description, sdkVersion, etc. You can find the options available for the app.json file here.

  • nano app.json

In the file, add this code:

app.json
{
  "expo": {
    "sdkVersion": "31.0.0",
    "name": "random-people",
    "slug": "random-people",
    "version": "0.1.0",
    "description": "An application for displaying random people",
    "primaryColor": "#ff8179"
  }
}

Let’s update the package.json file to include commands for running our application on Android and iOS emulators. Also, we’ll include the main field referencing the App.js file. This file will act as the entry file for the expo-cli. Open the package.json file in your editor:

  • nano package.json

Modify the file so it looks like this:

    // package.json
    {
      "name": "random-people",
      "version": "0.1.0",
      "private": true,
      "main": "./App.js",
      ...
      "scripts": {
        "start-web": "react-scripts start",
        "build-web": "react-scripts build",
        "test-web": "react-scripts test",
        "eject-web": "react-scripts eject",
        "start-native" : "expo start",
        "android": "expo android",
        "ios": "expo ios",
        "build:ios": "expo build:ios",
        "build:android": "expo build:android",
      },
      ...
    }

Run npm run start-web to run the application and visit http://localhost:3000 to view the application.

Step 2 – Creating the Home component

Our application is a simple demo that displays users via the Random User API. Using the API, we’ll get display a name and avatar of the returned users through some of the components provided by React Native. Within the src/ directory, create a file named home.js.

  • nano src/home.js

Add this code to the file to define the component:

src/home.js
import React from "react";
import {
  ScrollView,
  ActivityIndicator,
  StyleSheet
} from "react-native";

class Home extends React.Component {
  state = {
    users: [],
    loading: true
  };
  componentDidMount() {
    // TODO: get users
  }

  render() {
    return (
      <ScrollView
        noSpacer={true}
        noScroll={true}
        style={styles.container}
      >
       <ActivityIndicator
            style={[styles.centering, styles.gray]}
            color="#ff8179"
            size="large"
          />
      </ScrollView>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    backgroundColor: "whitesmoke"
  },
  centering: {
    alignItems: "center",
    justifyContent: "center",
    padding: 8,
    height: '100vh'
  },
});

export default Home;

The Home component renders a ScrollView component that houses the component’s elements. Currently, the component displays an ActivityIndicator; this will be replaced by the user list when the call to the API is complete.

We create styles for the elements using the StyleSheet component. This allows us to style the component using properties similar to CSS properties.

Let’s create a method that gets random users from the Random User API. This method will be called during the componentDidMount lifecycle. Update the home.js component to include the getUsers method:

src/home.js
import React from 'react';
...

class Home extends React.Component {
  state = {
    ...
  };
  componentDidMount() {
    this.getUsers();
  }

  async getUsers() {
    const res = await fetch("https://randomuser.me/api/?results=20");
    const { results } = await res.json();
    this.setState({ users: [...results], loading: false });
  }
  ...
}

We can easily make requests using the native Fetch API. Results from the request are parsed and added to state. When the request is complete, we’ll hide the ActivityIncidator by setting loading to false.

Step 3 – Creating the App component

The AppComponent holds the logic for the application. We’ll update the default view created by Create React App to suit that of our application by adding logic to display native components.

Create a new file named App.js in the root of your project.

  • nano App.js

This file will be similar to the src/App.js file. The root App.js file will act as the entry file for Expo, and the src/App.js file exists for Create React App builds. Add this code to App.js in the root of your project:

App.js
import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  View,
} from 'react-native';
import Home from './home'

class App extends React.Component {
  render() {
    return (
      <View style={styles.appContainer}>
        <Home />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1,
  },
});

AppRegistry.registerComponent('App', () => App);

export default App;

In the snippet above, we register our App component using the AppRegistry. The AppRegistry is the entry point of React Native applications.

Now open src/App.js in your editor and add the same code to that file.

Now move on to creating the User item.

Step 4 – Creating the User Item

Each user item will be displayed using a View component. The View component is an important building block that supports layout using flexbox, styling and accessibility. The View component of each item will be within a SwipeableFlatList. Each item will display the user’s avatar, name and email. Create a file called user-item.js within the src/ folder:

  • nano src/user-item.js

Add the following code to the file:

user-item.js]
import React from "react";
import { View, Image, Text, StyleSheet } from "react-native";

const UserItem = ({ item: user }) => {
  return (
    <View style={styles.row}>
      <Image style={styles.rowIcon} source={user.picture.medium} />
      <View style={styles.rowData}>
        <Text style={styles.rowDataText}>{`${user.name.title} ${
          user.name.first
        } ${user.name.last}`}</Text>
        <Text style={styles.rowDataSubText}>{user.email}</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  row: {
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    padding: 15,
    marginBottom: 5,
    backgroundColor: "white",
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: "rgba(0,0,0,0.1)"
  },
  rowIcon: {
    width: 64,
    height: 64,
    marginRight: 20,
    borderRadius: "50%",
    boxShadow: "0 1px 2px 0 rgba(0,0,0,0.1)"
  },
  rowData: {
    flex: 1
  },
  rowDataText: {
    fontSize: 15,
    textTransform: "capitalize",
    color: "#4b4b4b"
  },
  rowDataSubText: {
    fontSize: 13,
    opacity: 0.8,
    color: "#a8a689",
    marginTop: 4
  }
});

export default UserItem;

To display the avatar of each user, we make use of the Image component. The component takes a source prop which acts the src which we are used to on the web. The component can be styled further as we have using styles.rowIcon property.

Next, we’ll create the UserList to display each UserItem.

Step 5 – Creating the Users List

The FlatList component is one that is performant in its list rendering methods. It lazy-loads the items within the list, and only loads more items when the user has scrolled to the bottom of the list. The SwipeableFlatList is a wrapper around the FlatList provided by React Native Web, which makes each item within the list swipeable — so each item will reveals a set of actions when swiped.

Let’s create the SwipeableFlatList for the users returned from the API. Import the SwipeableFlatList component from the react-native package and update the render function to display the list. Create a file called user-list.js in the src directory:

  • nano src/user-list.js

Add this code to the file:

src/user-list.js
import React from "react";
import { SwipeableFlatList } from "react-native";
import UserItem from "./user-item";

const UserList = ({ users }) => {
  return (
    <SwipeableFlatList
      data={users}
      bounceFirstRowOnMount={true}
      maxSwipeDistance={160}
      renderItem={UserItem}
    />
  );
};

export default UserList;
  • data: this prop represents the data that will be fed to each item within the list. The data prop is usually an array.
  • bounceFirstRowOnMount: if true, it triggers on a bounce animation on the first item in the list, signifying that it has hidden actions within.
  • maxSwipeDistance: this prop sets a maximum swipeable distance for each item.
  • Finally, the renderItem prop takes a function that renders an item; this function will be passed an item prop that contains the data to be displayed.

Let’s update the src/home.js file to include the new UserList. Open the /src/home.js file and update it with the following:

src/home.js
import React from "react";
import { ScrollView, StyleSheet, ActivityIndicator } from "react-native";
import UserList from "./user-list";

class Home extends React.Component {
  state = {
    users: [],
    loading: true
  };
 ...
  render() {
    return (
      <ScrollView noSpacer={true} noScroll={true} style={styles.container}>
        {this.state.loading ? (
          <ActivityIndicator
            style={[styles.centering]}
            color="#ff8179"
            size="large"
          />
        ) : (
          <UserList users={this.state.users} />
        )}
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  ...
});
export default Home;

Now if you visit http://localhost:3000 on your browser, you will see a list of users.

We’re using a SwipeableFlatList component which means each user item is swipeable, so let’s add actions that you can swipe to reveal.

Step 6 – Adding Actions to Items

Each item within the list will be provided a set of actions that will be revealed when swiped to the left. The actions set will use the TouchableHighlight component encompassed by the View component. The TouchableHighlight component is used when we require viewers to respond to touches, more or less acting like a button. Create a file named user-actions.js in the src/ folder.

  • nano src/user-actions.js

Add the following contents to the file:

src/user-actions.js
import React from "react";
import {
  View,
  TouchableHighlight,
  Text,
  Alert,
  StyleSheet
} from "react-native";

const styles = StyleSheet.create({
  actionsContainer: {
    flex: 1,
    flexDirection: "row",
    justifyContent: "flex-end",
    alignItems: "center",
    padding: 10
  },
  actionButton: {
    padding: 10,
    color: "white",
    borderRadius: 6,
    width: 80,
    backgroundColor: "#808080",
    marginRight: 5,
    marginLeft: 5
  },
  actionButtonDestructive: {
    backgroundColor: "#ff4b21"
  },
  actionButtonText: {
    textAlign: "center"
  }
});

const UserActions = () => {
  return (
    <View style={styles.actionsContainer}>
      <TouchableHighlight
        style={styles.actionButton}
        onPress={() => {
          Alert.alert("Tips", "You could do something with this edit action!");
        }}
      >
        <Text style={styles.actionButtonText}>Edit</Text>
      </TouchableHighlight>
      <TouchableHighlight
        style={[styles.actionButton, styles.actionButtonDestructive]}
        onPress={() => {
          Alert.alert(
            "Tips",
            "You could do something with this remove action!"
          );
        }}
      >
        <Text style={styles.actionButtonText}>Remove</Text>
      </TouchableHighlight>
    </View>
  );
};

export default UserActions;

The TouchableHighlight component takes an onPress callback that is triggered when the component is clicked. Each callback triggers an Alert display. Styles are also applied to the encompassing View component and other components on the page.

To include the actions on each user item, update the UserList component to include the renderQuickActions prop, which also takes a function. Open src/user-list.js and modify it:

src/user-list.js
import React from "react";
...
import UserActions from "./user-actions";

const UserList = ({ users }) => {
  return (
    <SwipeableFlatList
      ...
      renderQuickActions={UserActions}
    />
  );
};

export default UserList;

Now when you swipe left on any user item it reveals two actions.

Step 7 – Adding a Header Component

Now that we’ve successfully fetched users and displayed them using native components, let’s liven the application by setting a header. Using the SafeAreaView component, we’ll create an area with defined boundaries. This will act as the header for our application. Create a new file called header.js in the src/ folder:

  • nano src/header.js

Add the following code to the file:

src/header.js
import React from 'react';
import {SafeAreaView, View, Text, StyleSheet} from 'react-native';

const Header = ({ onBack, title }) => (
  <SafeAreaView style={styles.headerContainer}>
    <View style={styles.header}>
      <View style={styles.headerCenter}>
        <Text accessibilityRole="heading" aria-level="3" style={styles.title}>{title}</Text>
      </View>
    </View>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  headerContainer: {
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: '#ff4e3f',
    backgroundColor: '#ff8179',
  },
  header: {
    padding: 10,
    paddingVertical: 5,
    alignItems: 'center',
    flexDirection: 'row',
    minHeight: 50
  },
  headerCenter: {
    flex: 1,
    order: 2
  },
  headerLeft: {
    order: 1,
    width: 80
  },
  headerRight: {
    order: 3,
    width: 80
  },
  title: {
    fontSize: 19,
    fontWeight: '600',
    textAlign: 'center',
    color: 'white'
  },
});

export default Header;

Now let’s add the Header component to the App component. This will display a simple header at the top of the application. Update the App.js file to include the Header component:

src/App.js
import React from 'react';
...
import Header from './header';

class App extends React.Component {
  render() {
    return (
      <View style={styles.appContainer}>
        <Header title="Random People" />
        <Home />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  ...
});

AppRegistry.registerComponent('App', () => App);

export default App;

After the application refreshes the header will be added to the top of the application.

Be sure to modify the App.js file in the root of your project with these changes as well, so it works with Expo when you test it.

Let’s see the various methods we can use to test the application on mobile.

Step 8 –Testing on Mobile

The expo-cliutility provides various method to test the application on mobile devices. The first is using a URL generated after running the application, this URL can be visited on your mobile browser to test the application.

To test the application on mobile, the expo-cli provides various methods to test the application mobile. The first is using a URL generated after running the application. This URL can be visited on your mobile browser to test the application.

Run the following command within your project to run the application with Expo:

  • npm run start-native

Expo typically starts your application on port 19002, so visit http://localhost:19002 to view the Expo dev tools. Within the dev tools you can send a link as an SMS or email to your mobile phone.

You can select any of the three connection options — an external tunnel, LAN or Local connection. For the local connection, your mobile phone and development computer have to be connected to the same network, but the tunnel works regardless.

The next option for testing on a mobile device is using a emulator. Using Android studio or Xcode, you can boot emulators for their respective platforms. Download and install the tool for the platform of choice — Xcode for iOS or Android studio for Android. After installation, run npm run android or npm run ios to start the application on any of the emulators.

Step 9 – Deploying the application

We’ll be deploying our application to the Android Play store. To achieve this, we’ll need to update the app.json file to include Android specific properties. Open the app.json file and update the file to include the android field:

app.json
{
  "expo": {
    "sdkVersion": "31.0.0",
    "name": "random-people",
    "slug": "random-people",
    "version": "0.1.0",
    "description": "An application for displaying random people",
    "primaryColor": "#ff8179",
    "android": {
      "package": "com.random.people"
    }
  }
}

The android.package field is a unique value that will represent your package in the app store. You can read more on the package-naming convention here. After updating the file, run the npm run build:android command.

This command will present you with a prompt, asking you to provide a keystore or to generate a new one. If you have an existing keystore, you can select this option or let expo generate one for your application.

After completion, a download link will be generated for your application. Clicking on this link will trigger a download for your APK.

To deploy the downloaded APKto the Android Play Store, visit the Play Console to create an account. After creating an account, you’ll be required to pay a registration fee of $25 before proceeding. After completing the registration process, visit this page and follow the steps to upload your application to the Play Store.

Conclusion

Using the React Native Web and React Native libraries, you created an application that can be deployed on several platforms using native components. Building multi-platform applications has never been easier. You can view the source code for the demo here.

0 Comments

Creative Commons License