Meteor and React Native - Create a native mobile app
Tags: javascript, meteor, reactnative, tutorial
Published: 2022-10-26 08:17:35


This is a full-scale workshop. Estimated time: 60min to 120min overall. We are going to create a working mobile app that includes authentication, a profile page and a managed ToDo-list. The app is developed with React-Native and uses a backend, implemented with Meteor.

<small>Photo used by <a href="https://unsplash.com/@rami_alzayat?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Rami Al-zayat</a> on <a href="https://unsplash.com/s/photos/mobile-app?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></small>
  

**Quick link to the final workshop code:**

{% embed https://github.com/jankapunkt/meteor-react-native-workshop %}

---
## Background

This workshop is a result from [my session at Meteor Impact 2022](https://impact.meteor.com/meetings/virtual/MKTBfAERWWS6kM3eS), which missed out the last part due to time constraints. Therefore I reviewed the workshop and the code and improved on everything. 👀

Get your Terminals and IDEs ready, grab a hot beverage and make yourself comfortable for a full hands-on session over the next two hours.

During the workshop we will cover these major topics:

- installation of the dev environment
- create the Meteor backend
- create the React Native app
- connect/reconnect the RN app to the backend
- full authentication workflow using react navigation
- CRUD a personal `Tasks` collection (using Meteor Methods and Publication/Subscription)

If you are new to Meteor or React Native, let me shortly introduce them to you (or skip to the next section if you already know).

![Meteor icon](https://raw.githubusercontent.com/devicons/devicon/master/icons/meteor/meteor-original.svg) 

[Meteor](https://meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) is a fullstack cross-platform framework to develop modern JavaScript applications. It can either be used with your favorite frontend engine/library to create SPAs (single page applications) with Websockets and [Publish/Subscribe](https://guide.meteor.com/data-loading.html/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors). Nevertheless, it also supports SSR, HTTP routes and integrates well with the vast majority of packages, installed from NPM, because under the hood it's all NodeJS.

![react logo](https://raw.githubusercontent.com/devicons/devicon/master/icons/react/react-original.svg)

[React Native](https://reactnative.dev/) is also a cross-platform framework but for developing native mobile apps by using one of the world's most famous JavaScript libraries: [React](https://reactjs.org/). But don't get it wrong! This is not just building some WebView-containered HTML5 app. It will render all your code using the mobile platform's native engine capabilities.

🤝 Together they provide a great developer experience for developing mobile apps. They both run on many platforms and deploy to different platforms at the same time. For example, we can run our development environment on Ubuntu Linux but run  our development build on an iOS device.

You can also check out and contribute to their repositories on GitHub:

{% embed https://github.com/meteor/meteor %}

{% embed https://github.com/facebook/react-native %} 

---
## Architecture overview

One more thing, before we start with our hands-on workshop. Let me provide an overview for those of you who prefer a visual. Consider the Meteor app as server (backend) and the React Native app as one of it's clients:
  

![architecture overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sw4jyg3472669trzrx5q.png)

As you can see the both will communicate using `@meteorrn/core`. The package implements most of the api as the Meteor client bundle on the web. However, it also provides an easy way to hook into some lower level API events that makes it in combination with `useEffect` very easy to act upon changes from the server.

The backend also manages the [authentication via `Accounts`](https://guide.meteor.com/accounts.html/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) automatically, while we will use a Navigation with React contexts on the client. Notice, that authentication will also use features from `@meteorrn/core` that allow us to retrieve the login token to implement an **"auto-login" functionality**.

Now, let's finally start the actual workshop. ⌨

---
## 1. Prerequisites 🔧🪚🪛

In order to make the most out of the workshop you should gather at least one physical mobile device. It doesn't matter, whether it's Android or iOS or even both, React Native and Expo will take care of the bundling details.

### 1.1 Install Expo Go (or use emulators)

You should install the `"Expo Go"` app on these devices as it will be a huge help to us when we will preview our development builds. You can find the App in the stores by searching for "Expo Go" or directly under these links:

#### Expo Go for iOS

{% embed https://itunes.apple.com/app/apple-store/id982107779 %}

#### Expo Go for Android


{% embed https://play.google.com/store/apps/details?id=host.exp.exponent&referrer=www %}

If one of the links won't work, you can also [get them from the Expo website](https://expo.dev/client).


> ❗❗❗ Notice, that you will not necessarily have to signup to use the Expo Go app, even if it looks like that at a first glance.

#### Install emulators as alternative

If you really don't have access to a physical device or you face certain issues with your devices during this workshop you might consider installing the respective emulators.

The Expo devs provide a comprehensive guide on installing the [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) or [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) for you.

### 1.2 Install Meteor

You can either install Meteor on various OS' and platforms via NPM (in case you have Node >= 14 and NPM already installed)

```bash
$ npm install -g meteor
```

or using their provided shell script on Linux or MacOS:

```bash
$ curl https://install.meteor.com/ | sh
```

If you want to install it on a Mac with M1, a docker container or Windows, please [read the details from the official installation guide](https://docs.meteor.com/install.html/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors). 

> ❗❗❗ Important
> In this workshop you have to use `meteor npm` in your terminals from now on, instead of just `npm`. It is necessary to always use the correct linked binaries. 
> This is due to the Meteor tool ships with a bundled NPM that has it's own path on your system. It gets removed, including all installed dependencies, once you remove Meteor.

Verify Meteor and it's NPM via

```bash
$ meteor --version
$ meteor npm -v
```

### 1.3 Install Expo CLI

React Native development is totally possible without Expo. However, with Expo it just starts to feel comfortable. No worries about building, no Android SDK or XCode to install. Hell, you can make a development build for iOS on a Linux machine. At that point they got me already.

> 📝 Notice

However, there are also a few downsides. First, you have to stick with the Expo Sdk and regularly update the Sdk.
Second, if you want to deploy your apps for the App Store / Play Store you will likely need to use their [cloud services (EAS)](https://expo.dev/eas). Allthough they offer a free tier, it may introduce costs and finally, this is still a dependency to a third party when it comes to your deployment. The good side though is, that this services takes care of the really nasty sides of mobile app deployment, so it might not be that bad at all.

Enough talk, let's install the Expo CLI tool globally:

```bash
$ meteor npm install -g expo-cli
```

> ❗❗❗ Important
> This causes the expo cli to be available only in the Meteor namespace, so you will need to call `meteor expo` instead of just `expo` later on.

### 1.4 Create project repository or local folder

Once the tools are installed we can actually create the new projects. In order to keep track of everything you can either create a new empty GitHub repository and clone it locally or create a local folder that you later connect with an existing repository. In both ways, let's name the project `mrntodos` for this workshop.

#### 1.4.a Clone from GitHub via SSH

```sh
$ git clone git@github.com:myusername/mrntodos.git
```

#### 1.4.b Clone from GitHub via HTTPS

```sh
$ git clone https://github.com/myusername/mrntodos.git
```

#### 1.4.c Create local folder

```sh
$ mkdir -p mrntodos
```

---
![Meteor icon](https://raw.githubusercontent.com/devicons/devicon/master/icons/meteor/meteor-original.svg) 

## 2. Create and set up the Meteor backend

Create a new Meteor project is straight forward using `meteor create [options] [name]`. However, with Meteor we have many choices for our frontend: React, Vue, Svelte, Solid (coming in 2.8), Blaze, Apollo, headless and some more ([read the docs for all options](https://docs.meteor.com/commandline.html#meteorcreate/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)). Meteor integrates them for you automatically on project creation.


> 📝 Notice
> The term "backend" is a bit misleading here. The Meteor application is a fullstack app, providing a server and client build for you. However, for this workshop we will only use the server environment, making the Meteor app act as the "backend" for the mobile app.

### 2.1 Create a new Meteor app
For the workshop we use Meteor's default create command (which uses React as frontend):

```sh
$ cd mrntodos # if you are not already in the project folder
$ meteor create backend
$ cd backend
$ echo "{}" > settings.json
```

We can configure global app settings in Meteor using a JSON file (`backend/settings.json`), that gets injected into the process environment by [starting meteor with `--settings`](https://docs.meteor.com/api/core.html#Meteor-settings/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors). It comes in very handy when you have to manage multiple deployments that differ in their configuration.

In order to make the Meteor app automatically start on a custom port and to use our `settings.json`, let's change one of the `scripts` in `backend/package.json`:

```json
...
"start": "meteor --port=8000 --settings=setting.json"
```

Now start the Meteor app via

```sh
$ meteor npm start
```

If you see the following message output, everything is fine and you can continue to the next section:

```sh
=> App running at: http://localhost:8000/
```

### 2.2 Add authentication layer to the Meteor backend

Meteor provides core packages for zero- to minimal-config authentication, named `Accounts`. Using `accounts-password` you get an out-of-the-box OAuth2 authentication and the servers also uses `bcrypt` to encrypt the password on the server.

Accounts also integrate [third-party authentication with Facebook, Twitter, Apple, GitHub](https://guide.meteor.com/accounts.html#oauth/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) or your [custom OAuth2 provider](https://github.com/leaonline/oauth2-server). However, in this workshop we add and configure the most basic authentication method, using `accounts-passwords`:

```bash
$ meteor add accounts-password
```

Accounts is also configurable ([documentation](https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config)) and we should change their defaults according to our needs. In order to do that, add a new file under `backend/imports/startup/server/accounts.js` (create these folders, if they don't exist yet). Then add the following to this file:

```js
import { Accounts } from 'meteor/accounts-base'
import { Meteor } from 'meteor/meteor'

// Here we define the fields that are automatically 
// available to clients via Meteor.user().
// This extends the defaults (_id, username, emails) 
// by our profile fields.
// If you want your custom fields to be immediately 
// available then place them here.
const defaultFieldSelector = {
  _id: 1,
  username: 1,
  emails: 1,
  firstName: 1,
  lastName: 1
}


// merge our config from settings.json with fixed code
// and pass them to Accounts.config
Accounts.config({ 
  ...Meteor.settings.accounts.config,
  defaultFieldSelector 
})
```

Now add the following parts to your `settings.json` file:

```json
{
  "accounts": {
    "config": {
      "forbidClientAccountCreation": true,
      "ambiguousErrorMessages": true,
      "sendVerificationEmail": true,
      "loginExpirationInDays": null
    }
  }
}
```

All [parameters of the configuration are documented](https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors), in case you want to further investigate on them.

> 📝 Notice
> The `accounts-base` package is automatically added when `accounts-password` is added. When importing `Accounts` in your code you will use `import { Accounts } from 'meteor/accounts-base'`.

**Finally,** make sure our startup file is imported in `backend/server/main.js`. Open this file and replace it's default content with:

```js
import '../imports/startup/server/accounts'
```

### 2.3 Add registration Method endpoint

By default Meteor Accounts allow clients to register themselves via `Accounts.createUser` ([docs](https://docs.meteor.com/api/passwords.html#Accounts-createUser/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)). This is 1:1 supported in the `@meteorrn/core` package, too.

However, we want to have additional functionality during the registration process, like sending an enrollment email and adding default profile fields to the user document.

Therefore, we create a new registration endpoint as Meteor Method. We create a new file in `backend/imports/accounts/methods.js` and add the following code to it:

```js
import { Accounts } from 'meteor/accounts-base'
import { check, Match } from 'meteor/check'

export const registerNewUser = function (options) {
  check(options, Match.ObjectIncluding({
    email: String,
    password: String,
    firstName: String,
    lastName: String,
    loginImmediately: Match.Maybe(Boolean)
  }))

  const { email, password, firstName, lastName, loginImmediately } = options

  if (Accounts.findUserByEmail(email)) {
    throw new Meteor.Error('permissionDenied', 'userExists', { email })
  }

  const userId = Accounts.createUser({ email, password })

  // we add the firstName and lastName as toplevel fields
  // which allows for better handling in publications
  Meteor.users.update(userId, { $set: { firstName, lastName } })

  // let them verify their new account, so
  // they can use the full app functionality
  Accounts.sendVerificationEmail(userId, email)

  if (loginImmediately) {
    // signature: { id, token, tokenExpires }
    return Accounts._loginUser(this, userId)
  }

  // keep the same return signature here to let clients
  // better handle the response
  return { id: userId, token: undefined, tokenExpires: undefined }
}
```

**Finally,** make sure this file is imported at startup by creating a new Meteor Method with it in in our already created `backend/imports/startup/server/accounts.js` file:

```js
import { Accounts } from 'meteor/accounts-base'
import { Meteor } from 'meteor/meteor'
import { registerNewUser } from '../../accounts/methods'

Accounts.config(Meteor.settings.accounts.config)
Meteor.methods({ registerNewUser })
```

### 2.6 Add more endpoints to manage accounts

If you also want users to update their profile or delete their account then you need an endpoint Method for them, too.

Let's extend the `backend/imports/accounts/methods.js` file by these two methods:

```js
// ... registerNewUser

export const updateUserProfile = function ({ firstName, lastName }) {
  check(firstName, Match.Maybe(String))
  check(lastName, Match.Maybe(String))

  // in a meteor Method we can access the current user
  // via this.userId which is only present when an
  // authenticated user calls a Method
  const { userId } = this

  if (!userId) {
    throw new Meteor.Error('permissionDenied', 'notAuthenticated', { userId })
  }

  const updateDoc = { $set: {} }

  if (firstName) {
    updateDoc.$set.firstName = firstName
  }

  if (lastName) {
    updateDoc.$set.lastName = lastName
  }

  return !!Meteor.users.update(userId, updateDoc)
}

export const deleteAccount = function () {
  const { userId } = this

  if (!userId) {
    throw new Meteor.Error('permissionDenied', 'notAuthenticated', { userId })
  }

  return !!Meteor.users.remove(userId)
}
```

**Finally,** update the startup file at `backend/imports/startup/server/accounts.js`:

```js
import { Accounts } from 'meteor/accounts-base'
import { Meteor } from 'meteor/meteor'
import { 
  registerNewUser,
  updateUserProfile,
  deleteAccount
 } from '../../accounts/methods'

// ... other code

Meteor.methods({
  registerNewUser,
  deleteAccount,
  updateUserProfile
})
```

At this point you have the minimal api defined for your app to sign up, sign in, sign out (builtin, via `Meteor.logout()`), delete account and update profile. Let's continue by building the mobile app now.

---

![react logo](https://raw.githubusercontent.com/devicons/devicon/master/icons/react/react-original.svg)

## 3. Create and set up the React Native app

In this section we will create a new React Native app with an Expo-managed workflow. Make sure you have set up all required tools from section 1. If you have no physical device available, you can also install the Android emulator or iOS simulator (also covered in section 1).

### 3.1 Create the new React Native app

First of all, **open a new terminal** in order to install and run the app. The backend and the app will use separate node processes. Therefore, to manage them both effectively you should work on them in separate terminals.

If you haven't installed `expo-cli` yet **or you have updated your Meteor version**, then you need to install it via:

```sh
$ meteor npm install -g expo-cli
$ meteor expo --version
6.0.6 # at the time of this workshop
```

Within the project root folder (`mrntodos/`), create a new expo project and answer the questions as following:

```sh
$ cd mrntodos # if not already there
$ meteor expo init
? What would you like to name your app? › app
? Choose a template: › - Use arrow-keys. Return to submit.
    ----- Managed workflow -----
❯   blank a minimal app as clean as an empty canvas
```

> 📝 Notice, that you can also use different workflows, for example if you prefer to use TypeScript. Choose on your own or stick with this workshop's preference.

Then we also need to install some dependencies here that will be necessary for us during the workshop:

```sh
$ cd app
$ meteor expo install @meteorrn/core @react-navigation/native @react-navigation/native-stack @react-navigation/stack expo-secure-store expo-status-bar react-native-screens react-native-safe-area-context react-native-gesture-handler
```

> ❗❗❗ Important
> We need to use `meteor expo install` to install our dependencies. This is, because Expo resolves the correct dependency versions for the current Sdk for us. By doing so, we don't need to fiddle with package versions that may break our builds or are fundamentally incompatible.

After adding the package you may get some warnings from the `npm audit`. If you are like me and care about this, **please help us to release the next version of `@meteorrn/core`** with updated dependencies by testing the latest commits with your local build. Add your review or issues to: https://github.com/meteorrn/meteor-react-native

## 3.2 Set proper network settings

Connecting your app to the backend requires some further configuration. You need to obtain your **local network ip** in order to make the RN app connect. The Meteor-typical `localhost` will not work here, since this would not resolve to the same local ip when the code runs on the mobile device.

You can usually obtain your local ip via one of these commands:

| os      | command        |
|---------|----------------|
| Linux   | `ip addr show` |
| MacOs   | `ifconfig`     |
| Windows | `ipconfig`     |

Once you obtained the ip (at home this is often a `192.168.178.XXX` or similar) please create a config file with this value: 

```sh
$ cd app # if not already there
$ echo "{}" > config.json
```

Place the following content in there (where `xxx.xxx.xxx.xxx` is replaced by your obtained local ip):

```json
{
  "backend": {
    "url": "ws://xxx.xxx.xxx.xxx:8000/websocket"
  }
}
```

At this point, **let's run the app the first time** to let Expo generate a few files via:

```bash
$ meteor npm start
```


Use your mobile device and scan the QR code with the Expo Go app. You should see now that bundling is in progress:


![bundling in progress image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5rdkup4fmhi6i10ffrgv.png)

Once this is done your device should display the default content from `app/App.js` afterwards:


![initial screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2u2ca322wvq9s9gc56n5.png)

After running, check the newly created `app/.expo/settings.json` file and make sure it looks like the following:

```json
{
  "hostType": "lan",
  "lanType": "ip",
  "dev": true,
  "minify": false,
  "urlRandomness": "mc-y7b",
  "https": false,
  "scheme": null,
  "devClient": false
}
```

## 3.3 Set Hermes as JavaScript engine

We can use a JavaScript engine, named Hermes, which is entirely optimized for React Native. In fact, we should. Consult the [React Native docs page on Hermes](https://reactnative.dev/docs/hermes) to understand why.

In order to activate it with Expo, we need to edit the settings in `app/app.json`:

```json
 "expo": {
    "name": "app",
    "slug": "app",
    "version": "1.0.0",
    "assetBundlePatterns": [
      "**/*"
    ],
    "jsEngine": "hermes"
  }
```

That's all and Expo takes care of the rest for us.

---
## 4. Connect the app to the Meteor backend

Nearly all functionality of our app requires a connection to our backend. Meteor establishes connections and exchanges data via [DDP, a custom protocol](https://docs.meteor.com/api/connections.html?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) that builds on top of Websockets. The great thing is, that you don't have to worry about this whole transport layer as Meteor abstracts all of this logic for you so you can focus on the important things.

## 4.1 Write a connection hook

We want to remain flexible with the connection and the way we deal with it's state. This is why we will abstract it into a custom [React hook](https://reactjs.org/docs/hooks-intro.html), which we name `useConnection`.

Create a new file at `app/src/hooks/useConnection.js` and add the following code there:

```js
import { useEffect, useState } from 'react'
import Meteor from '@meteorrn/core'
import * as SecureStore from 'expo-secure-store'
import config from '../../config.json'

// get detailed info about internals
Meteor.isVerbose = true

// connect with Meteor and use a secure store
// to persist our received login token, so it's encrypted
// and only readable for this very app
// read more at: https://docs.expo.dev/versions/latest/sdk/securestore/
Meteor.connect(config.backend.url, {
  AsyncStorage: {
    getItem: SecureStore.getItemAsync,
    setItem: SecureStore.setItemAsync,
    removeItem: SecureStore.deleteItemAsync
  }
})

export const useConnection = () => {
  const [connected, setConnected] = useState(null)
  const [connectionError, setConnectionError] = useState(null)

  // we use separate functions as the handlers, so they get removed
  // on unmount, which happens on auto-reload and would cause errors
  // if not handled
  useEffect(() => {
    const onError = (e) => setConnectionError(e)
    Meteor.ddp.on('error', onError)

    const onConnected = () => connected !== true && setConnected(true)
    Meteor.ddp.on('connected', onConnected)

    // if the connection is lost, we not only switch the state
    // but also force to reconnect to the server
    const onDisconnected = () => {
      Meteor.ddp.autoConnect = true
      if (connected !== false) {
        setConnected(false)
      }
      Meteor.reconnect()
    }
    Meteor.ddp.on('disconnected', onDisconnected)

    // remove all of these listeners on unmount
    return () => {
      Meteor.ddp.off('error', onError)
      Meteor.ddp.off('connected', onConnected)
      Meteor.ddp.off('disconnected', onDisconnected)
    }
  }, [])

  return { connected, connectionError }
}
```

Classic Meteor + React provides a `useTracker` hook for Reactivity. In this case, we hook directly into the DDP events in order to update the connection state.

### 4.3 Integrate the `useConnection` hook into `App.js`

Let's move two levels up and open `app/App.js`, our main entry point for our React app. Here, we now integrate the `useConnection` hook and render messages, based on the connection state:

```jsx
import React from 'react'
import { MainNavigator } from './src/screens/MainNavigator'
import { StyleSheet, View, Text, ActivityIndicator } from 'react-native'
import { useConnection } from './src/hooks/useConnection'

export default function App () {
  const { connected, connectionError } = useConnection()

  // use splashscreen here, if you like
  if (!connected) {
    return (
      <View style={styles.container}>
        <ActivityIndicator />
        <Text>Connecting to our servers...</Text>
      </View>
    )
  }

  // use alert or other things here, if you like
  if (connectionError) {
    return (
      <View style={styles.container}>
        <Text>Error, while connecting to our servers!</Text>
        <Text>{connectionError.message}</Text>
      </View>
    )
  }

  return (
      <View style={styles.container}>
        <Text>We are connected!</Text>
      </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#efefef',
    alignItems: 'center',
    justifyContent: 'center'
  }
})
```

Start the app and see the `We are connected!` message on the screen. Now switch to the other terminal for our backend and hit `ctrl+c` to cancel the process. Take a look at your device - it will immediately show `Connecting to our servers...` as it tries to reconnect. Now start the backend server again and see the immediate change back to the connected state.


![connecting to servers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/382ruobue7mms50wogdm.png)

You have a reactive connection status now 🥳 You could use this hook even further to build a offline-first app but that won't be covered by our workshop.

From here we can move on to the authentication workflow, but before that we need to do a short refactoring!

### 4.4 Tidy up code

There are a few elements that we will reuse a few more times in this app. It's a good practice to not repeat to write the same code all over again.

One potential saving is to create a default stylesheet, which you can compare to having a main.css file on the web. It helps to provide a consistent layout across our app and a single point to manage this layout.

Let's create a new file at `app/src/styles/defaultStyles.js` and place the following content there:

```js
import { StyleSheet } from 'react-native'

export const defaultColors = {
  placeholder: '#8a8a8a',
  danger: '#981111',
  white: '#eee',
  black: '#1a1a1a',
  primary: '#0B52AF'
}

export const defaultStyles = StyleSheet.create({
  text: {
    height: 40,
    margin: 12,
    borderWidth: 1,
    padding: 10,
    alignSelf: 'stretch',
    color: defaultColors.black,
    backgroundColor: defaultColors.white
  },
  panel: {
    margin: 20
  },
  container: {
    margin: 20,
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'
  },
  danger: {
    color: defaultColors.danger
  },
  dangerBorder: {
    borderWidth: 1,
    borderColor: defaultColors.danger
  },
  bold: {
    fontWeight: 'bold'
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center'
  },
  flex1: {
    flex: 1
  }
})
```

Second, let's create a new `ErrorMessage` component. It will also use some of the default styles already. Create the new file at `app/src/components/ErrorMessage.js` and add the following code to it:

```jsx
import React from 'react'
import { Text, View } from 'react-native'
import { defaultStyles } from '../styles/defaultStyles'

export const ErrorMessage = ({ error, message }) => {
  if (!error && !message) { return null }

  return (
    <View style={defaultStyles.container}>
      <Text style={defaultStyles.danger}>{message || error.message}</Text>
    </View>
  )
}
```

With both of these abstractions we can, again, update `app/App.js` and replace

```jsx
// use alert or other things here, if you like
if (connectionError) {
  return (
    <View style={styles.container}>
      <Text>Error, while connecting to our servers!</Text>
      <Text>{connectionError.message}</Text>
    </View>
  )
}
```

with

```jsx
// use alert or other things here, if you like
if (connectionError) {
  return (<ErrorMessage error={connectionError} />)
}
```

---
## 5. Implement the authentication workflow

One of the most important parts of our application is to provide a fluent and accessible authentication workflow. Users should not have to sign in each time they use the app. Rather a login token should be stored securely (which is why we installed `expo-secure-store`) and used until expiration (which we defined in section 2 using `Accounts.config`).

If users don't have an account, they should sign up by providing email, password, first name and last name. Once the account is created, they should receive the login token and become signed-in automatically. Finally, users should be able to sign out and delete their account.

The following graphics summarizes the auth workflow from a more abstract perspective, including the screens to navigate:


![authentication workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qn123j2njqxaqt5e6ee.png)

This workflow is inspired by the [React Nativation authentication workflow](https://reactnavigation.org/docs/auth-flow/) and we will use this library to implement the workflow within our own environmental boundaries.


## 5.1 Create the authentication context and api layer

As with the connection we want to decouple authentication from our rendering logic as much as possible in React. This also helps to keep the project not tightly coupled to any Meteor logic. At the same time we want to have a unified place of where the authentication state is managed.

### 5.1.1 Create a new authentication context

React contexts provides a way to access our auth layer without the need to passing it down through the whole component tree and helps to prevent tight coupling. Read more in the [React docs on contexts](https://reactjs.org/docs/context.html), if you want to understand how it works in details.

Since there will be multiple screens, that make use of our authentication, we create and export our authentication context at `app/src/contexts/AuthContext.js`:

```js
import { createContext } from 'react'

export const AuthContext = createContext()
```

### 5.2.2 Create a `useAuth` hook

The whole "magic" will happen here. This is where calls from the components will be passed through as DDP requests to the backend and responses will update the state accordingly. In contrast, errors will be passed back to the components to make them render error messages.

Due to the all this resulting in a more complex state we should use a [React reducer](https://reactjs.org/docs/hooks-reference.html#usereducer). Our authentication functions should also be only created once, which is why we wrap them in a `useMemo` hook.

Create the file `app/src/hooks/useAuth.js` and add the following content:

```js
import { useReducer, useEffect, useMemo } from 'react'
import Meteor from '@meteorrn/core'

const initialState = {
  isLoading: true,
  isSignout: false,
  userToken: null
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'RESTORE_TOKEN':
      return {
        ...state,
        userToken: action.token,
        isLoading: false
      }
    case 'SIGN_IN':
      return {
        ...state,
        isSignOut: false,
        userToken: action.token
      }
    case 'SIGN_OUT':
      return {
        ...state,
        isSignout: true,
        userToken: null
      }
  }
}

const Data = Meteor.getData()


export const useAuth = () => {
  const [state, dispatch] = useReducer(reducer, initialState, undefined)

  // Case 1: restore token already exists
  // MeteorRN loads the token on connection automatically,
  // in case it exists, but we need to "know" that for our auth workflow
  useEffect(() => {
    const handleOnLogin = () => dispatch({ type: 'RESTORE_TOKEN', token: Meteor.getAuthToken() })
    Data.on('onLogin', handleOnLogin)
    return () => Data.off('onLogin', handleOnLogin)
  }, [])


  const authContext = useMemo(() => ({
    signIn: ({ email, password, onError }) => {
      Meteor.loginWithPassword(email, password, async (err) => {
        if (err) {
          if (err.message === 'Match failed [400]') {
            err.message = 'Login failed, please check your credentials and retry.'
          }
          return onError(err)
        }
        const token = Meteor.getAuthToken()
        const type = 'SIGN_IN'
        dispatch({ type, token })
      })
    },
    signOut: ({ onError }) => {
      Meteor.logout(err => {
        if (err) {
          return onError(err)
        }
        dispatch({ type: 'SIGN_OUT' })
      })
    },
    signUp: ({ email, password, firstName, lastName, onError }) => {
      const signupArgs = { email, password, firstName, lastName, loginImmediately: true }

      Meteor.call('registerNewUser', signupArgs, (err, credentials) => {
        if (err) {
          return onError(err)
        }

        // this sets the { id, token } values internally to make sure
        // our calls to Meteor endpoints will be authenticated
        Meteor._handleLoginCallback(err, credentials)

        // from here this is the same routine as in signIn
        const token = Meteor.getAuthToken()
        const type = 'SIGN_IN'
        dispatch({ type, token })
      })
    },
    deleteAccount: ({ onError }) => {
      Meteor.call('deleteAccount', (err) => {
        if (err) {
          return onError(err)
        }

        // removes all auth-based data from client
        // as if we would call signOut
        Meteor.handleLogout()
        dispatch({ type: 'SIGN_OUT' })
      })
    }
  }), [])

  return { state, authContext }
}
```

> 📝 Notice, that this file requires some explanation, of course.

- the `reducer` handles the internal state, manipulated via `dispatch` calls
- the `@meteorrn/core` packages will (once connected) automatically check for an existing token in the provided secure store; if found, it will try to login with token and emit a ' onLogin'  event, if that succeeded; we can leverage this in the `useEffect` hook to implement our "auto-login" feature
- the `authContext` is retrieved in screens with the previously created `AuthContext` and `useContext` as you will see in the upcoming sections
- the several functions `signIn`, `signOut`, `signUp` and `deleteAccounts` leverage mostly what the `@meteorrn/core` package uses internally during `Meteor.loginWithPassword` or `Meteor.logout`
- you could replace their code entirely if you need to migrate off Meteor (I hope you don't :heart:)


## 5.2 Create the screens

In the following step we will create all necessary screens that are involved in the workflow.

All screens should be created in `app/src/screens`, which you can create via

```sh
$ cd app # if not already in app folder
$ mkdir -p src/screens
```

### 5.2.1 HomeScreen

The home screen at `app/src/screens/HomeScreen.js` will contain nothing but a message for now:

```jsx
import React from 'react'
import { View, Text } from 'react-native'
import { defaultStyles } from '../styles/defaultStyles'

export const HomeScreen = () => {
  return (
    <View style={defaultStyles.container}>
      <Text>Welcome home!</Text>
    </View>
  )
}
```

### 5.2.2 LoginScreen

The login screen provides a simple input for email and password, where the password field uses `secureTextEntry` to hide characters.

If the authentication fails, there should be an error message being displayed. If the user no account yet, the "Sign up" button should trigger the navigation to signup:

```jsx
import React, { useState, useContext } from 'react'
import { View, Text, TextInput, Button } from 'react-native'
import { AuthContext } from '../contexts/AuthContext'
import { defaultColors, defaultStyles } from '../styles/defaultStyles'
import { ErrorMessage } from '../components/ErrorMessage'

export const LoginScreen = ({ navigation }) => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState(null)
  const { signIn } = useContext(AuthContext)

  // handlers
  const onError = err => setError(err)
  const onSignIn = () => signIn({ email, password, onError })
  const onSignUp = () => navigation.navigate('SignUp')

  // render login form
  return (
    <View style={defaultStyles.container}>
      <TextInput
        placeholder='Your Email'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={email}
        onChangeText={setEmail}
      />
      <TextInput
        placeholder='Password'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      <ErrorMessage error={error} />
      <Button title='Sign in' color={defaultColors.primary} onPress={onSignIn} />
      <View style={defaultStyles.panel}>
        <Text>or</Text>
      </View>
      <Button title='Sign up' onPress={onSignUp} color={defaultColors.placeholder} />
    </View>
  )
}
```

> 📝 Notice two things here. 

First, the `signIn` method will be provided by `useContext`, where `AuthContext` acts as kind of a 'key' to make react return the correct value. Second, the component props will contain a `navigation` property, which is always injected when we use [React Navigation](https://reactnavigation.org/).

### 5.2.3 RegistrationScreen

The register screen provides a similar form but will use a different auth method:

```jsx
import React, { useContext, useState } from 'react'
import { TextInput, Button, View } from 'react-native'
import { defaultColors, defaultStyles } from '../styles/defaultStyles'
import { AuthContext } from '../contexts/AuthContext'
import { ErrorMessage } from '../components/ErrorMessage'

export const RegistrationScreen = () => {
  const [email, setEmail] = useState()
  const [firstName, setFirstName] = useState()
  const [lastName, setLastName] = useState()
  const [password, setPassword] = useState()
  const [error, setError] = useState()
  const { signUp } = useContext(AuthContext)

  const onError = err => setError(err)
  const onSignUp = () => signUp({ email, password, firstName, lastName, onError })

  return (
    <View style={defaultStyles.container}>
      <TextInput
        placeholder='Your Email'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={email}
        onChangeText={setEmail}
      />
      <TextInput
        placeholder='Your password'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      <TextInput
        placeholder='Your first name (optional)'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={firstName}
        onChangeText={setFirstName}
      />
      <TextInput
        placeholder='Your last name (optional)'
        placeholderTextColor={defaultColors.placeholder}
        style={defaultStyles.text}
        value={lastName}
        onChangeText={setLastName}
      />
      <ErrorMessage error={error} />
      <Button title='Create new account' onPress={onSignUp} />
    </View>
  )
}
```

### 5.2.4 ProfileScreen

The profile screen will only be available, once authenticated. For now we create on this screen only the sign-out and delete-account features.

```jsx
import { defaultColors, defaultStyles } from '../styles/defaultStyles'
import { Button, Text, View } from 'react-native'
import { useState } from 'react'
import { ErrorMessage } from '../components/ErrorMessage'

export const ProfileScreen = () => {
  const [error, setError] = useState(null)
  const handleSignOut = () => console.log('sign out')
  const handleDelete = () => console.log('delete account')


  return (
    <View style={defaultStyles.container}>
      <View style={{ ...defaultStyles.dangerBorder, padding: 10, marginTop: 10, alignSelf: 'stretch' }}>
        <Text style={defaultStyles.bold}>Danger Zone</Text>
        <Button title='Sign out' color={defaultColors.danger} onPress={handleSignOut} />
        <Button title='Delete account' color={defaultColors.danger} onPress={handleDelete} />
        <ErrorMessage error={error} />
      </View>
    </View>
  )
}
```

## 5.3 Implement the navigation

As you might have realized already there are no "cancel" or "back" buttons on the screens and no "titles" rendered. This will all be handled by our [React Navigation](https://reactnavigation.org/) library. It allows us to remain flexible in the way screens are connected to each other.

On top of that, it helps us to render different screens, based on our authentication state and provides options on how the navigation bar is rendered.

### 5.3.1 Create the main navigation

Let's create the navigation at `app/src/screens/MainNavigator.js` and add the following code:

```jsx
import React from 'react'
import { CardStyleInterpolators } from '@react-navigation/stack'
import { AuthContext } from '../contexts/AuthContext'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { useAuth } from '../hooks/useAuth'
import { HomeScreen } from './HomeScreen'
import { LoginScreen } from './LoginScreen'
import { RegistrationScreen } from './RegistrationScreen'
import { ProfileScreen } from './ProfileScreen'
import { NavigateButton } from '../components/NavigateButton'

const Stack = createNativeStackNavigator()

export const MainNavigator = () => {
  const { state, authContext } = useAuth()
  const { userToken } = state

  const renderScreens = () => {
    if (userToken) {
      // only authenticated users can visit these screens
      const headerRight = () => (<NavigateButton title='My profile' route='Profile' />)
      return (
        <>
          <Stack.Screen name='Home' component={HomeScreen} options={{ title: 'Welcome home', headerRight }} />
          <Stack.Screen name='Profile' component={ProfileScreen} options={{ title: 'Your profile' }} />
        </>
      )
    }

    // non authenticated users need to sign in or register
    // and can only switch between the two screens below:
    return (
      <>
        <Stack.Screen
          name='SignIn'
          component={LoginScreen}
          options={{ title: 'Sign in to awesome-app' }}
        />
        <Stack.Screen
          name='SignUp'
          component={RegistrationScreen}
          options={{ title: 'Register to awesome-app' }}
        />
      </>
    )
  }

  return (
    <AuthContext.Provider value={authContext}>
      <NavigationContainer>
        <Stack.Navigator screenOptions={{ cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS }}>
          {renderScreens()}
        </Stack.Navigator>
      </NavigationContainer>
    </AuthContext.Provider>
  )
}
```


Again, this code needs some further explanation:

- the `Stack` is a navigator that renders a native push/pop animation when navigation back and forth; it looks really good out-of-the-box
- `state` is the current state from the `reducer` in `useAuth`, while the `authContext` is injected into our components tree via `<AuthContext.Provider value={authContext}>`; without this it would not be accessible within the screens using `useContext`
- the `userToken` is only present, when `@meteorrn/core` received a token or loaded it from the secure store and sucessfully signed in with it

### 5.3.2 Create a navigation button component

After adding the navigation, please also add a new `NavigateButton` component at `app/src/components/NavigateButton.js` and add the following code to it:

```jsx
import { Button } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { defaultColors } from '../styles/defaultStyles'

export const NavigateButton = ({ title, route }) => {
  const navigation = useNavigation()

  return (
    <Button
      title={title}
      color={defaultColors.primary}
      onPress={() => navigation.navigate(route)}
    />
  )
}
```

It helps us to avoid passing down `navigation` through the whole components tree and handles routing internally.

### 5.3.3 Integrate the navigation into `App.js`

There will be nothing new, if we start the app at this stage. This is because we need to integrate the `MainNavigation` into our `App`. Therefore, update `app/App.js` to the following final code:

```jsx
import React from 'react'
import { MainNavigator } from './src/screens/MainNavigator'
import { View, Text, ActivityIndicator } from 'react-native'
import { useConnection } from './src/hooks/useConnection'
import { ErrorMessage } from './src/components/ErrorMessage'
import { defaultStyles } from './src/styles/defaultStyles'

export default function App () {
  const { connected, connectionError } = useConnection()

  // use splashscreen here, if you like
  if (!connected) {
    return (
      <View style={defaultStyles.container}>
        <ActivityIndicator />
        <Text>Connecting to our servers...</Text>
      </View>
    )
  }

  // use alert or other things here, if you like
  if (connectionError) {
    return (<ErrorMessage error={connectionError} />)
  }

  return (<MainNavigator />)
}
```

## 5.4 Test all workflow steps

At this point you should be able to run the app and test the whole authentication workflow. Here are some screenshots of what to expect:

### 5.4.1 LoginScreen

Plain on enter:

![sign in screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4p5ewxs7zmvdjru9zz57.png)

When sign in failed:


![sign in failed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vda6khtgc7ad4iz0che6.png)


### 5.4.2 RegistrationScreen


![register screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6mcmmfjzqb5q7ikigyt.png)

### 5.4.3 HomeScreen


![home screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5aynhwvgcxbpirqc5lr.png)

### 5.4.4 ProfileScreen


![profile screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6z2posy1p2bz8ojmbhsb.png)

## 5.5 Add a minimal user profile

This step provides an important insight on handling data reactivity from the Meteor backend in a way, that decouples from the rendering.

We want to update our user's profile (right now just the `firstName` and `lastName` fields) and reflect these changes **immediately without the need to manually fetch the updated profile**!

## 5.5.1 Create a `useAccount` hook

In order to do that we first create a new hook, named `useAccount` at `app/src/hooks/useAccount.js` and add the following code:

```js
import Meteor from '@meteorrn/core'
import { useMemo, useState } from 'react'

const { useTracker } = Meteor

export const useAccount = () => {
  const [user, setUser] = useState(Meteor.user())

  useTracker(() => {
    const reactiveUser = Meteor.user()
    if (reactiveUser !== user) {
      setUser(reactiveUser)
    }
  })

  const api = useMemo(() => ({
    updateProfile: ({ options, onError, onSuccess }) => {
      Meteor.call('updateUserProfile', options, (err) => {
        return err
          ? onError(err)
          : onSuccess()
      })
    }
  }), [])

  return { user, ...api }
}
```

With this hook we create a similar (but not same) approach as with our auth context. However, in this case we want to keep the `updateProfile` functionality close to the user profile and therefore won't use a context here.

Note the `useTracker`, which is one of the fundamental pillars of Meteor's reactivity model. In combination with publish/subscribe we can receive an updated user profile in the moment the server updated it on the db-level.

> 📝 Notice, that you have not subscribed to any publications, yet the user document automatically updates when changed on the server. This is due to Meteor always auto-publishes changes to a user's own document and why we set `defaultPublishFields` for `Accounts.config` in section 2.

## 5.5.2 Integrate the `useAccount` hook into the `ProfileScreen`

In order to update the profile fields we need to provide another simple form and handle some of it's state. I won't go too deep into details here as this is only apply the dry principle to have a form for multiple fields.

This is now the updated `app/screens/ProfileScreen.js` file:

```jsx
import { AuthContext } from '../contexts/AuthContext'
import { defaultColors, defaultStyles } from '../styles/defaultStyles'
import { Button, Text, TextInput, View, StyleSheet } from 'react-native'
import { useContext, useState } from 'react'
import { ErrorMessage } from '../components/ErrorMessage'
import { useAccount } from '../hooks/useAccount'

export const ProfileScreen = () => {
  const [editMode, setEditMode] = useState('')
  const [editValue, setEditValue] = useState('')
  const [error, setError] = useState(null)
  const { signOut, deleteAccount } = useContext(AuthContext)
  const { user, updateProfile } = useAccount()
  const onError = err => setError(err)

  if (!user) {
    return null // if sign our or delete
  }

  /**
   * Updates a profile field from given text input state
   * by sending update data to the server and let hooks
   * reactively sync with the updated user document. *magic*
   * @param fieldName {string} name of the field to update
   */
  const updateField = ({ fieldName }) => {
    const options = {}
    options[fieldName] = editValue
    const onSuccess = () => {
      setError(null)
      setEditValue('')
      setEditMode('')
    }
    updateProfile({ options, onError, onSuccess })
  }

  const renderField = ({ title, fieldName }) => {
    const value = user[fieldName] || ''

    if (editMode === fieldName) {
      return (
        <>
          <Text style={styles.headline}>{title}</Text>
          <View style={defaultStyles.row}>
            <TextInput
              placeholder={title}
              autoFocus
              placeholderTextColor={defaultColors.placeholder}
              style={{ ...defaultStyles.text, ...defaultStyles.flex1 }}
              value={editValue}
              onChangeText={setEditValue}
            />
            <ErrorMessage error={error} />
            <Button title='Update' onPress={() => updateField({ fieldName })} />
            <Button title='Cancel' onPress={() => setEditMode('')} />
          </View>
        </>
      )
    }

    return (
      <>
        <Text style={styles.headline}>{title}</Text>
        <View style={{ ...defaultStyles.row, alignSelf: 'stretch' }}>
          <Text style={{ ...defaultStyles.text, flexGrow: 1 }}>{user[fieldName] || 'Not yet defined'}</Text>
          <Button
            title='Edit' onPress={() => {
              setEditValue(value)
              setEditMode(fieldName)
            }}
          />
        </View>
      </>
    )
  }

  return (
    <View style={defaultStyles.container}>
      <Text style={styles.headline}>Email</Text>
      <Text style={{ ...defaultStyles.text, alignSelf: 'stretch' }}>{user.emails[0].address}</Text>

      {renderField({ title: 'First Name', fieldName: 'firstName' })}
      {renderField({ title: 'Last Name', fieldName: 'lastName' })}

      <Text style={styles.headline}>Danger Zone</Text>
      <View style={{ ...defaultStyles.dangerBorder, padding: 10, marginTop: 10, alignSelf: 'stretch' }}>
        <Button title='Sign out' color={defaultColors.danger} onPress={() => signOut({ onError })} />
        <Button title='Delete account' color={defaultColors.danger} onPress={() => deleteAccount({ onError })} />
        <ErrorMessage error={error} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  headline: {
    ...defaultStyles.bold,
    alignSelf: 'flex-start'
  }
})
```

---
## 6. Simple ToDos with CRUD functionality

This could now be the early end of the workshop, if you intend to go another path and continue from here with your own code. However, for those who want to stay and implement a simple ToDo app, hang tight. It's just another few steps.

### 6.1 Add Tasks functionality to the backend

The very heart of our app's functionality is always what the backend provides. Therefore we start by defining in the backend what Methods and Publications are actually available.

### 6.1.1 Add a new Tasks collection

We begin by creating a new Mongo collection, which is in Meteor just one line of code. Add the following code to `backend/imports/tasks/TasksCollection.js`:

```js
import { Mongo } from 'meteor/mongo'

export const TasksCollection = new Mongo.Collection('tasks')
```

### 6.1.2 Add Task Method endpoints and Publications

Client's should be able to create new tasks, update their `checked` state or remove them. Data fetching should be realized by publish/subscribe.

A simple implementation could look the the following `backend/imports/tasks/methods.js` file:

```js
import { Meteor } from 'meteor/meteor'
import { TasksCollection } from './TasksCollection'

export const getMyTasks = function () {
  const userId = this.userId
  checkUser(userId)
  return TasksCollection.find({ userId })
}

export const insertTask = function ({ text }) {
  const userId = this.userId
  checkUser(userId)
  const checked = false
  const createdAt = new Date()
  return TasksCollection.insert({ text, userId, checked, createdAt })
}

export const checkTask = function ({ _id, checked }) {
  const userId = this.userId
  checkUser(userId)
  return TasksCollection.update({ _id, userId }, { $set: { checked } })
}

export const removeTask = function ({ _id }) {
  const userId = this.userId
  checkUser(userId)
  return TasksCollection.remove({ _id, userId })
}

const checkUser = userId => {
  if (!userId) {
    throw new Meteor.Error('permissionDenied', 'notSignedIn', { userId })
  }
}
```

### 6.1.3 Add Tasks to the startup

First, register the Methods and Publication in a new file, `backend/imports/startup/server/tasks.js`:

```js
import { Meteor } from 'meteor/meteor'
import { checkTask, insertTask, getMyTasks, removeTask } from '../../tasks/methods'

Meteor.methods({
  'tasks.insert': insertTask,
  'tasks.setIsChecked': checkTask,
  'tasks.remove': removeTask
})

Meteor.publish('tasks.my', getMyTasks)
```

Finally, make sure this file is imported in `backend/server/main.js`:

```js
import '../imports/startup/server/accounts'
import '../imports/startup/server/tasks'
```

At this point the backend is ready to handle your personal tasklists aka "simple todos".

### 6.2 Add Tasks functionality to the app

This section involves the communication with the backend and rendering of the tasks. It contains a bit more code than the previous one. However, we will introduce only one new concept, the `Meteor.subscribe` mechanism. Apart from that, it's all things you already know by now.

#### 6.2.1 Install Checkbox component

Prior to this workshop React Native contained an own `Checkbox` component, which is now deprecated. While there are multiple UI component systems out there that provide great checkboxes, let's stick with our current environment and use one, that Expo provides:

```sh
$ meteor expo install expo-checkbox
```

#### 6.2.2 Add a client Mongo Collection

One of the core aspects of Meteor is isomorphism - certain concepts implement the same API and behavior on the client as on the server. The same applies to the data handling, which is realized by "Minimongo" a lightweight counterpart to the server's Mongo Collections. It is a core part of Meteor's publish-subscribe system and a source for data reactivity.

In order to make use of these effects, the client collection needs to be named the same way as the collection in the server publication. Add therefore the following code to the new file `app/src/tasks/TasksCollection.js`:

```js
import { Mongo } from '@meteorrn/core'

export const TasksCollection = new Mongo.Collection('tasks')
```

#### 6.2.3 Create a Task component

Since we are creating a Task List it is good to have each Task rendered using an own component. It should display the task's text and checked status and also provide a way to pass up some actions to the parents. For the checkbox we will use the previously installed `expo-checkbox`.

Create a new file `app/src/tasks/Task.js` with the following content:

```jsx
import React from 'react'
import { View, Text, Button, StyleSheet } from 'react-native'
import Checkbox from 'expo-checkbox'
import { defaultColors } from '../styles/defaultStyles'

export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
  const handleCheck = (checked) => onCheckboxClick({ _id: task._id, checked })

  return (
    <View style={{ display: 'flex', flexDirection: 'row', width: '100%', padding: 5, justifyContent: 'space-between' }}>
      <View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
        <Checkbox
          value={task.checked}
          onValueChange={handleCheck}
          style={{ padding: 12 }}
          color={task.checked && defaultColors.placeholder}
          readOnly
        />
        <Text style={task.checked ? styles.checked : styles.unchecked}>{task.text}</Text>
      </View>
      <Button title='X' onPress={() => onDeleteClick(task)} style={{ justifySelf: 'flex-end' }} />
    </View>
  )
}

const styles = StyleSheet.create({
  checked: {
    color: defaultColors.placeholder,
    marginLeft: 10
  },
  unchecked: {
    marginLeft: 10
  }
})
```

In this case we will bubble-up the handlers to the parent, because of the way we will render the task items in the list.

#### 6.2.4 Create a Task form

We also need a form to create new tasks. It is very similar to what we already did before. Create a new file at `app/src/tasks/TaskForm.js` and add the following content:

```jsx
import Meteor from '@meteorrn/core'
import React, { useState } from 'react'
import { View, Button, TextInput } from 'react-native'
import { defaultColors, defaultStyles } from '../styles/defaultStyles'
import { ErrorMessage } from '../components/ErrorMessage'

export const TaskForm = () => {
  const [text, setText] = useState('')
  const [error, setError] = useState('')
  const handleSubmit = e => {
    e.preventDefault()
    if (!text) return
    Meteor.call('tasks.insert', { text }, (err) => {
      if (err) {
        return setError(err)
      }
      setError(null)
    })
    setText('')
  }

  return (
    <View>
      <View style={defaultStyles.row}>
        <TextInput
          placeholder='Type to add new tasks'
          value={text}
          place
          onChangeText={setText}
          placeholderTextColor={defaultColors.placeholder}
          style={defaultStyles.text}
        />
        <Button title='Add Task' onPress={handleSubmit} />
      </View>
      <ErrorMessage error={error} />
    </View>
  )
}
```

#### 6.2.5 Combine them in a Task list component

Now it's time to create a new component at `app/src/tasks/TaskList.js` and make all the prior created files work together, providing the complete CRUD functionality for our simple todos.

```jsx
import Meteor from '@meteorrn/core'
import React, { useState } from 'react'
import { Text, View, SafeAreaView, FlatList, Button, ActivityIndicator } from 'react-native'
import { TasksCollection } from './TasksCollection'
import { Task } from './Task'
import { TaskForm } from './TaskForm'
import { useAccount } from '../hooks/useAccount'
import { defaultColors, defaultStyles } from '../styles/defaultStyles'

const { useTracker } = Meteor
const toggleChecked = ({ _id, checked }) => Meteor.call('tasks.setIsChecked', { _id, checked })
const deleteTask = ({ _id }) => Meteor.call('tasks.remove', { _id })

export const TaskList = () => {
  const { user } = useAccount()
  const [hideCompleted, setHideCompleted] = useState(false)

  // prevent errors when authentication is complete but user is not yet set
  if (!user) { return null }

  const hideCompletedFilter = { checked: { $ne: true } }
  const userFilter = { userId: user._id }
  const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilter }

  const { tasks, pendingTasksCount, isLoading } = useTracker(() => {
    const tasksData = { tasks: [], pendingTasksCount: 0 }

    if (!user) {
      return tasksData
    }

    const handler = Meteor.subscribe('tasks.my')

    if (!handler.ready()) {
      return { ...tasksData, isLoading: true }
    }

    const filter = hideCompleted
      ? pendingOnlyFilter
      : userFilter
    const tasks = TasksCollection.find(filter, { sort: { createdAt: -1 } }).fetch()
    const pendingTasksCount = TasksCollection.find(pendingOnlyFilter).count()

    return { tasks, pendingTasksCount }
  }, [hideCompleted])

  if (isLoading) {
    return (
      <View style={defaultStyles.container}>
        <ActivityIndicator />
        <Text>Loading tasks...</Text>
      </View>
    )
  }

  const pendingTasksTitle = `${pendingTasksCount ? ` (${pendingTasksCount})` : ''}`

  return (
    <SafeAreaView style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <View style={{ display: 'flex', flexDirection: 'column', flexGrow: 1, overflow: 'scroll' }}>
        <View style={defaultStyles.row}>
          <Text>My Tasks {pendingTasksTitle}</Text>
          <TaskForm />
        </View>
        <Button
          title={hideCompleted ? 'Show All' : 'Hide Completed Tasks'}
          color={defaultColors.placeholder}
          onPress={() => setHideCompleted(!hideCompleted)}
        />
        <FlatList
          data={tasks}
          renderItem={({ item: task }) => (
            <Task
              task={task}
              onCheckboxClick={toggleChecked}
              onDeleteClick={deleteTask}
            />
          )}
          keyExtractor={task => task._id}
        />

      </View>
    </SafeAreaView>
  )
}
```

The Meteor magic happens here within the `useTracker` hook. There we subscribe to the backend's publication `tasks.my` and apply optional post-filter to the retrieved data. We do all that by manipulation the live-synced Mongo.Collection. The `useTracker` will also trigger a new render cycle on data updates and this provides the "automagical" feeling of live updates.

One last step and we are finally done! Make sure, the `TaskList` is included in the `HomeScreen`:

```jsx
import React from 'react'
import { View } from 'react-native'
import { defaultStyles } from '../styles/defaultStyles'
import { TaskList } from '../tasks/TaskList'

export const HomeScreen = () => {
  return (
    <View style={defaultStyles.container}>
      <TaskList />
    </View>
  )
}
```


### 6.3 Finally celebrate your first app 🥳

![final image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqojqlwi7w2sac7yoaff.png)

---
## 7. Summary and outlook

In this workshop we created a fully functional mobile app with React Native and Meteor as it's backend. It tackles the biggest challenges of making them both work together - connection, authentication and communication.

There are many topics to continue from here, since this is just a minimal app prototype. Many topics can't be covered in just one workshop, which is why I tried to shrink them down to the "essentials". If there are any uncovered topics that you think are essential, too, then please leave a comment.

If you liked the workshop, please like and subscribe to my channel.

---
### Further Resources and links

#### Meteor

- [Website](https://www.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Hosting](https://www.meteor.com/cloud/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Guide](https://guide.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [API Docs](https://docs.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Twitter](https://twitter.com/meteorjs)
- [Facebook](https://www.facebook.com/meteorjs)
- [Instagram](https://www.instagram.com/meteor.js)
- [YouTube](https://www.youtube.com/user/MeteorVideos)
- [LinkedIn](https://www.linkedin.com/company/meteor-software)
- [Slack](https://social.meteor.com/Slack)
- [Podcast](https://podcast.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Medium (Blog)](https://blog.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Forums](https://forums.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)
- [Github](https://github.com/meteor/meteor)

#### React Native

- [React Native](https://reactnavigation.org/docs/auth-flow/)
- [React](https://reactjs.org/)
- [Expo](https://expo.dev)
- [React Navigation](https://reactnavigation.org/)
- [Auth Workflows](https://reactnavigation.org/docs/auth-flow/)


---
## About me

I regularly publish articles here on dev.to about **Meteor** and **JavaScript**. If you like what you are reading and want to support me, you can [sponsor me on GitHub](https://github.com/jankapunkt) or [send me a tip via PayPal](https://paypal.me/kuesterjan).

You can also find (and contact) me on [GitHub](https://github.com/jankapunkt/), [Twitter](https://twitter.com/kuester_jan) and [LinkedIn](https://www.linkedin.com/in/jan-kuester/).

Keep up with the latest development on Meteor by visiting [their blog](https://blog.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) and if you are the same into Meteor like I am and want to show it to the world, you should check out the [Meteor merch store](https://shop.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors).