Creating “Quarantine Pro” — A Fun Learning Experiment in React Native

By Júlia Rocha 14 min read

Creating Quarantine Pro is a fun way to beat boredom.

Learning Experiments in React Native: COVID-19 has changed our way of life since early 2020. Self-isolating seems to be the best thing you can do right now. So, while isolated, we came up with a fun, simple React Native app idea.

The app tracks how long you have been quarantining. Enter the date you started isolating, and the app displays a fun message showing how far you have come in the quarantine game.

Beyond being fun, this tutorial uses the latest SDK version 37.x.x of Expo to build a demo app. You will learn the following three points:

Here is a sneak peek of what we intend to build in this tutorial:

Quarantine-Pro-React-Native-app-example

You can find the complete code for this tutorial in this GitHub repo.

Create a New Expo App

Start by creating a new Expo app at your favorite side-project location in your local development environment. Run the following command from a new terminal window to generate a React Native app using Expo-cli.

npx expo-cli init DaVinciOfIsolation

When asked to select a template, choose blank from a Managed workflow.
choose-blank-template-from-the-Managed-workflow

After that, press Enter and let Expo-cli install the dependencies required to start this project.

Once the project has initialized, go to the project directory from the terminal window and start the Expo bundler service.

expo start

This will start the Expo app on a simulator or device where the Expo client is installed. For more information on installing an Expo client, visit the official documentation.

Once the app is running in the Expo client, the following default screen welcomes you:
welcoming-default-screen-to-start-working-on-your-app

Let us install the npm dependencies we are going to need to build this project by executing the following command:

expo install expo-font @use-expo/font @react-native-community/datetimepicker

Expo Install adds dependencies using a version compatible with the Expo SDK.

Also, install the following npm packages either using npm install or using yarn:

yarn add react-native-modal-datetime-picker moment

With that, we have installed the required set of npm dependencies. Let us move further and start building the app.

How To Use Custom Fonts In An Expo App

Install a New Font

In this app, we’ll use a custom font that is free to download from Google Fonts (Press Start 2P).

To use this font or any other custom font, create a new directory called fonts inside the assets or folder. Then place the font file(s) you have just downloaded. The path to the fonts directory ./assets/fonts is a convention that Expo developers recommend using when placing custom fonts in your app.

After placing the file inside the newly created directory, the file structure will look like below:
file-structure-example-after-placing-the-file-inside-the-newly-created-directory

When you are downloading a font to use in an Expo React Native app, make sure you download either a font either.otf .otf or .ttf format. These two formats work across all Expo platforms, including the web, iOS, and Android.

Use the useFonts Hook

To use any hook in a React or React Native app, you have to use functional components. To set up a new font, start by importing the following statements:

import React from 'react';
import { View, Text } from 'react-native';
import { useFonts } from '@use-expo/font';
import { AppLoading } from 'expo';

The useFonts hook takes one argument as a JavaScript object and returns a single-item list containing a value telling you whether the font is loaded or not. This eliminates a lot of boilerplate code for this check.

After you have imported the statements, create a new object called customFont. It will have a key ⁠— the name of the font itself- and the value of this key is the path to the font file in the assets/fonts/ directory.

const customFont = {
  'Press-Start2p': require('./assets/fonts/PressStart2P-Regular.ttf')
};

Next, inside the function component, define the isLoaded variable from the useFonts hook and pass the customFont object as its argument.

Also, when the font is loading or hasn’t loaded yet, it is good practice to use Expo’s AppLoading component and render nothing on the screen.

Once the font loads, the screen will display the functional component’s content.

Here is the complete code for the App component. Right now, we display the app title using the new font we just installed.

export default function App() {
  const [isLoaded] = useFonts(customFont);

  if (!isLoaded) {
    return <AppLoading />;
  }

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        backgroundColor: '#ffbd12'
      }}
    >
      <Text
        style={{
          fontFamily: 'Press-Start2p',
          fontSize: 24,
          marginTop: 80,
          paddingHorizontal: 20
        }}
      >
        {`Are You a Quarantine Pro?`}
      </Text>
    </View>
  );
}

From the above snippet, make sure you describe the fontFamily property on the Text component. This is the only way the font is going to be used for a specific text component.

Go back to the Expo client, and you are going to see the following result:

That’s it! You have completed the first step of loading fonts and using them in a React Native app.

For more information, check out Cedric van Putten’s collection of hooks that you can use in an Expo app.
are-you-a-quarantine-pro

Create a Button To Use the Date Picker Modal

Since we have already installed the required npm dependencies to show a date picker modal (which uses the native date picker module), let us add a button to the current App.js file to display this modal.

Start by modifying the import statements as stated below and adding new ones.

import React, { useState } from 'react';
import {
  View,
  Text,
  StyleSheet,
  Dimensions,
  TouchableWithoutFeedback
} from 'react-native';
import {
  Fontisto,
  MaterialCommunityIcons,
  FontAwesome
} from '@expo/vector-icons';
import DateTimePickerModal from 'react-native-modal-datetime-picker';

To set the width and height of the button, we are going to use the Dimensions API from the React Native core. The button’s width and height will be calculated based on the current window width.

Define a variable W to represent the window width before the App functional component.

const W = Dimensions.get('window').width;

Next, after the app’s title text, define another container View component for the button. We’ll wrap the contents of the TouchableWithoutFeedback button in a separate View component because this React Native touchable component can only have one child.

However, we will have two child components: the button icon and the text. Modify the App component’s return statement as shown in the code snippet below.

return (
  <View style={styles.container}>
    <Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
    <TouchableWithoutFeedback>
      <View style={styles.pickerContainer}>
        <Fontisto style={styles.icon} name="calendar" size={48} />
        <Text style={styles.pickerText}>{`Tap here tonselect a date`}</Text>
      </View>
    </TouchableWithoutFeedback>
  </View>
);

Add the following styles to the code snippet above: use the StyleSheet object to manage styles in the current component file.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#ffbd12'
  },
  title: {
    fontFamily: 'Press-Start2p',
    fontSize: 24,
    marginTop: 80,
    paddingHorizontal: 20,
    lineHeight: 30
  },
  pickerContainer: {
    marginTop: 20,
    backgroundColor: '#00c6ae',
    width: W / 1.2,
    height: W / 4,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#000',
    borderBottomWidth: 5,
    borderBottomColor: '#000',
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row'
  },
  pickerText: {
    fontFamily: 'Press-Start2p',
    fontSize: 14,
    paddingHorizontal: 10,
    lineHeight: 20
  },
  icon: {
    color: '#000'
  }
});

Refresh the Expo client to get the following result:
result-from-refreshing-the-expo-client

Now, let us bind the date picker modal to this button. We already imported the react-native-modal-datetime-picker npm package we need for this step.

We are using this library over the default @react-community/react-native-datetimepicker because it exposes a cross-platform interface for showing the native date picker and time picker inside a modal.

For our app, we’ll also evaluate how many days the user has already spent in quarantine based on the date they choose.

Let us define a few state variables using the useState hook from React for the following reasons:

  • pickedDate to store the date picked by the user;
  • isDatePickerVisible to show or hide the date picker modal.

We also need to define three helper functions alongside these state variables. The first two will handle the date picker modal’s visibility.

The third will handle the confirm button in the date picker modal and decide what action to take when the user chooses a date.

Here, we hide the date picker modal and store the selected date in the pickedDate state variable.

export default function App() {
  // ... rest of the component remains same

  const [pickedDate, setPickedDate] = useState(null);
  const [isDatePickerVisible, setDatePickerVisibility] = useState(false);

  function showDatePicker() {
    setDatePickerVisibility(true);
  }

  function hideDatePicker() {
    setDatePickerVisibility(false);
  }

  function handleConfirm(date) {
    console.log('A date has been picked: ', date);
    hideDatePicker();
    setPickedDate(date);
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
      <TouchableWithoutFeedback onPress={showDatePicker}>
        <View style={styles.pickerContainer}>
          <Fontisto style={styles.icon} name="calendar" size={48} />
          <Text style={styles.pickerText}>{`Tap here tonselect a date`}</Text>
        </View>
      </TouchableWithoutFeedback>
      <DateTimePickerModal
        isVisible={isDatePickerVisible}
        mode="date",. 
        onConfirm={handleConfirm}
        onCancel={hideDatePicker}
        headerTextIOS="When did you start isolating?"
      />
    </View>
}

The showDatePicker method is going to be triggered every time a user taps the button to display the picker modal. The component will only render on the device’s screen when this method triggers.

When the user taps anywhere outside the modal or taps the Cancel button, the modal hides, and nothing else happens.

Quarantine Pro React Native App

However, when a date is selected and the user taps Confirm, we can take further action. Let’s log the date the user picked to the console.

The output appears on the Expo server running in the terminal window. This means that the user input is now stored in the state variable pickedDate.

You can also use other props available in @react-community/react-native-datetimepicker. In the date picker modal we are implementing, we’re making a small customization using the headerTextIOS prop. This prop allows changing the title of the picker modal for iOS devices.

Evaluate The “Quarantine Score”

The second missing piece of the puzzle in our current app is a button to calculate the day(s) difference between the user’s input and the current date (we will use this as our “quarantine score”).

We’ll follow the same strategy design-wise as in the previous section. Display a button that users can tap to see their score.

Start by importing the Moment library in the App.js file after the rest of the import statements. It will handle the calculation between the user’s input and the current date.

// rest of the import statements
import moment from 'moment';

This library will also help us format the date picker modal input and display only the date (not the time) in YYYY-MM-DD format.

Modify the return statement by adding a new View container with a text message and a button to calculate the difference in days.

Also, before modifying the functional component’s return statement, add a helper method called daysRemaining() to calculate the difference.

We are going to store this difference in a state variable called days. This state variable is going to be used in the next section to display the correct result on the screen.

We will calculate the difference between the pickedDate (the user’s input) and the todaysDate (the current date).

export default function App() {
const [days, setDays] = useState('');

function daysRemaining() {
  // user's input
    let eventdate = moment(pickedDate);
    // getting current date
    let todaysdate = moment();
    let remainingDays = todaysdate.diff(eventdate, 'days');
    setDays(remainingDays);
    return remainingDays;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
      <TouchableWithoutFeedback onPress={showDatePicker}>
        <View style={styles.pickerContainer}>
          <Fontisto style={styles.icon} name="calendar" size={48} />
          <Text style={styles.pickerText}>{`Tap here tonselect a date`}</Text>
        </View>
      </TouchableWithoutFeedback>
      <DateTimePickerModal
        isVisible={isDatePickerVisible}
        mode="date"
        onConfirm={handleConfirm}
        onCancel={hideDatePicker}
        headerTextIOS="When did you start isolating?"
      />
      {/* ADD BELOW */}
      <View style={styles.showDateContainer}>
        <Text style={styles.showDateText}>
          You started isolating on{' '}
          {pickedDate && (
            <Text style={styles.showDateText}>
              {moment(pickedDate).format('YYYY-MM-DD')}.
            </Text>
          )}
        </Text>
        <TouchableWithoutFeedback onPress={daysRemaining}>
          <View style={styles.evaluateButtonContainer}>
            <Text style={styles.evaluateButtonText}>Check your level</Text>
          </View>
        </TouchableWithoutFeedback>
      </View>
    </View>
}

The picked date is displayed in the desired format using moment().format() functions. The pickedDate will only show once the user has provided input by selecting the date from the date picker modal.

Here are the corresponding styles for the above snippet:

const styles = StyleSheet.create({
  // rest of the styles remain same
  showDateContainer: {
    marginTop: 20,
    backgroundColor: '#F95A2C',
    width: W / 1.2,
    height: W / 2,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#000',

    alignItems: 'center'
  },
  showDateText: {
    fontFamily: 'Press-Start2p',
    fontSize: 14,
    padding: 10,
    marginTop: 20,
    lineHeight: 20
  },
  evaluateButtonContainer: {
    marginTop: 20,
    backgroundColor: '#1947E5',
    width: W / 1.4,
    height: W / 6,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#000',
    borderBottomWidth: 5,
    borderBottomColor: '#000',
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row'
  },
  evaluateButtonText: {
    color: '#fff',
    fontFamily: 'Press-Start2p',
    fontSize: 14,
    paddingHorizontal: 10,
    lineHeight: 20
  },
}

Select the date from the picker modal. After the date has been picked, it will be shown below:

Quarantine Pro React Native App

Rendering The “Quarantine Level”

The last part of this app is to display the result when the user presses the “Check your level” button.

Modify the return statement of the App component. When the result is available, show the user’s quarantine level; when it’s not, display a default message in this UI box. Just after the previous section’s snippet, add another View container component.

When the evaluation is done, renderAchievements() will return only the icon and the text message based on the score (the difference between the current date and the user’s input date).

Since we use a state variable called days to store this difference, it’s easy to conditionally render the message.

export default function App() {
// rest of the code remains the same

function renderAchievements() {
    if (days > 1 && days < 5) {
      return (
        <>
          <MaterialCommunityIcons
            name="guy-fawkes-mask"
            color="#000"
            size={54}
          />
          <Text style={styles.resultText}>
            Quarantine Noob. Don't forget to wear a mask. Keep self-isolating.
          </Text>
        </>
      );
    } else if (days >= 5 && days <= 7) {
      return (
        <>
          <MaterialCommunityIcons name="glass-wine" color="#000" size={54} />
          <Text style={styles.resultText}>Quarantine Connoisseur. Welcome to the (literal) dark side!</Text>
        </>
      );
    } else if (days >= 8 && days <= 15) {
      return (
        <>
          <MaterialCommunityIcons
            name="seat-legroom-reduced"
            color="#000"
            size={54}
          />
          <Text style={styles.resultText}>Quarantine Proficient. AKA “What is pants?”</Text>
        </>
      );
    } else if (days >= 16 && days <= 22) {
      return (
        <>
          <MaterialCommunityIcons
            name="star-circle-outline"
            color="#000"
            size={54}
          />
          <Text style={styles.resultText}>Quarantine Veteran. #StayHome became your life motto.</Text>
        </>
      );
    } else if (days >= 23) {
      return (
        <>
          <FontAwesome name="paint-brush" color="#000" size={54} />
          <Text style={styles.resultText}>THE ULTIMATE QUARANTINE PRO! You are part of the solution - thank you!</Text>
        </>
      );
    } else
      return (
        <Text style={styles.resultText}>Your level will be shown here.</Text>
      );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
      <TouchableWithoutFeedback onPress={showDatePicker}>
        <View style={styles.pickerContainer}>
          <Fontisto style={styles.icon} name="calendar" size={48} />
          <Text style={styles.pickerText}>{`Tap here tonselect a date`}</Text>
        </View>
      </TouchableWithoutFeedback>
      <DateTimePickerModal
        isVisible={isDatePickerVisible}
        mode="date"
        onConfirm={handleConfirm}
        onCancel={hideDatePicker}
        headerTextIOS="When did you start isolating?"
      />
      <View style={styles.showDateContainer}>
        <Text style={styles.showDateText}>
          You started isolating on{' '}
          {pickedDate && (
            <Text style={styles.showDateText}>
              {moment(pickedDate).format('YYYY-MM-DD')}.
            </Text>
          )}
        </Text>
        <TouchableWithoutFeedback onPress={daysRemaining}>
          <View style={styles.evaluateButtonContainer}>
            <Text style={styles.evaluateButtonText}>Check your level</Text>
          </View>
        </TouchableWithoutFeedback>
      </View>

      {/* ADD BELOW */}

      <View style={styles.resultContainer}>{renderAchievements()}</View>
    </View>
}

Here are the styles for renderAchievements().

const styles = StyleSheet.create({
  // rest of the styles remain same
  resultContainer: {
    marginTop: 20,
    backgroundColor: '#FF89BB',
    width: W / 1.2,
    height: W / 2,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#000',
    justifyContent: 'center',
    alignItems: 'center'
  },
  resultText: {
    color: '#fff',
    fontFamily: 'Press-Start2p',
    fontSize: 16,
    padding: 15,
    lineHeight: 20
  }
});

Now, go back to the Expo client, and you will be welcomed by our final app! Run the app and select different dates to see different results, as shown below.

Quarantine Pro React Native App

Conclusion

We hope you had fun building this app and learning. The main objectives of this tutorial are complete and summarized below for clarity.

  • How to use the Expo font hook
  • How to use the DateTime picker modal to select a date
  • Use Moment.js to convert the user-provided date input and calculate the difference from the current date (“quarantine score”).

Check out @react-native-community/datetimepicker for more information on customizing the date picker modal, or try using a time picker. The Moment.js library is full of functions to help you manage dates and time in JavaScript apps.

The hosted demo is no longer available. You can explore the app source code on GitHub.