Building Dad Jokes using The Prisma Framework (formerly Prisma 2) and React Native
Tags: javascript, reactnative, graphql, react
Published: 2020-01-15 15:16:58

The Prisma Framework (formerly known as Prisma 2) is a complete rewrite of the original Prisma. It is being rewritten in Rust while the original was written in Scala. The original version had memory issues with it and it required JVM to run. It also needed an additional server to run in addition to a backend server. The newest version of Prisma does not require any such thing. With The Prisma Framework, the query engine is now a bundled executable that is run alongside the backend on the same server.

The Prisma Framework consists of 3 standalone tools to tackle the problems of data access, data migrations, and admin UI:

- [Photon](https://photonjs.prisma.io/): Type-safe and auto-generated database client ("ORM replacement")
- [Lift](https://lift.prisma.io/): Declarative migration system with custom workflows
- [Studio](https://github.com/prisma/studio): Provides Admin UI to support various database workflows

So now let's get started with building a server with [The Prisma Framework](https://github.com/prisma/prisma2).

To keep it fun and corny, we will be making a Dad Jokes App.

## Prerequisites

For this tutorial, you need a basic knowledge of [React Native](https://facebook.github.io/react-native/). You also need to understand [React Hooks](https://reactjs.org/docs/hooks-intro.html).

Since this tutorial is primarily focused on Prisma, it is assumed that you already have a working knowledge of React and its basic concepts.

Throughout the course of this tutorial, we’ll be using `yarn`. If you don’t have `yarn` already installed, install it from [here](https://yarnpkg.com/en/docs/install).

To make sure we’re on the same page, these are the versions used in this tutorial:

- Node v12.12.0
- npm v6.11.3
- npx v6.11.3
- yarn v1.19.1
- prisma2 v2.0.0-preview016.2
- expo-cli v3.7.1
- expo v35.0.0

## Server-Side (The Prisma Framework)

### Start a new Prisma 2 project

Install `prisma2` CLI globally and run the `init` command then:

```bash
$ yarn global add prisma2 // or npm install --global prisma2
$ prisma2 init server
```

### Run the interactive `prisma2 init` flow & select boilerplate

Select the following in the interactive prompts:

1. Select **Starter Kit**
2. Select **JavaScript**
3. Select **GraphQL API**
4. Select **SQLite**

Once terminated, the `init` command will have created an initial project setup in the `server/` folder.

Now open the `schema.prisma` file and replace it with the following:

```graphql
generator photon {
  provider = "photonjs"
}

datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

model Joke {
  id   String @default(cuid()) @id
  joke String @unique
}
```

`schema.prisma` contains the data model as well as the configuration options.

Here, we specify that we want to connect to the SQLite datasource called `dev.db` as well as target code generators like `photonjs` generator.

Then we define the data model `Joke` which consists of `id` and `joke`.

`id` is a primary key of type `String` with a default value of [cuid()](https://github.com/ericelliott/cuid).

`joke` is of type `String` but with a constraint that it must be unique.

Open `seed.js` file and paste the following:

```js
const { Photon } = require('@generated/photon')
const photon = new Photon()

async function main() {
  const joke1 = await photon.jokes.create({
    data: {
      joke:
        'Did you hear the one about the guy with the broken hearing aid? Neither did he.',
    },
  })
  const joke2 = await photon.jokes.create({
    data: {
      joke:
        'My dog used to chase people on a bike a lot. It got so bad I had to take his bike away.',
    },
  })
  const joke3 = await photon.jokes.create({
    data: {
      joke: "I don't trust stairs. They're always up to something.",
    },
  })
  const joke4 = await photon.jokes.create({
    data: {
      joke:
        "Dad died because he couldn't remember his blood type. I will never forget his last words. Be positive.",
    },
  })
  console.log({ joke1, joke2, joke3, joke4 })
}

main()
  .catch(e => console.error(e))
  .finally(async () => {
    await photon.disconnect()
  })
```

We are basically adding `jokes` into our SQLite database.

Now go inside `src/index.js` file and remove the contents of it. We'll start adding content from scratch.

First go ahead and import the necessary packages and declare some constants:

```js
const { GraphQLServer } = require('graphql-yoga')
const {
  makeSchema,
  objectType,
  queryType,
  mutationType,
  idArg,
  stringArg,
} = require('nexus')
const { Photon } = require('@generated/photon')
const { nexusPrismaPlugin } = require('nexus-prisma')
```

We have declared a constant `photon` which instantiates a new `Photon` class.

Let us declare our `Joke` model. Paste the code below it:

```js
const Joke = objectType({
  name: 'Joke',
  definition(t) {
    t.model.id()
    t.model.joke()
  },
})
```

We make use of `objectType` from the `nexus` package to declare `Joke`.

The `name` parameter should be the same as defined in the `schema.prisma` file.

The `definition` function lets you expose a particular set of fields wherever `Joke` is referenced. Here, we expose `id` and `joke` field.

If we expose only `joke` field, then `id` will not get exposed and only `joke` will get exposed wherever `Joke` is referenced.

Below that paste the `Query` constant:

```js
const Query = queryType({
  definition(t) {
    t.crud.joke()
    t.crud.jokes()
  },
})
```

We make use of `queryType` from the `nexus` package to declare `Query`.

The Photon generator generates an API that exposes CRUD functions on `Joke` model. This is what allows us to expose `t.crud.joke()` and `t.crud.jokes()` method.

We can also write `t.crud.jokes()` as follows:

```js
t.list.field('jokes', {
  type: 'Joke',
  resolve: (_, _args, ctx) => {
    return ctx.photon.jokes.findMany()
  },
})
```

Both the above code and `t.crud.jokes()` will give the same results.

In the above code, we make a field named `jokes`. The return `type` is `Joke`. We then call `ctx.photon.jokes.findMany()` to get all the jokes from our SQLite database.

> Note that the name of the `jokes` property is auto-generated using the [pluralize](https://github.com/blakeembrey/pluralize) package. It is therefore recommended to name our models singular i.e. `Joke` and not `Jokes`.

We use the `findMany` method on `jokes` which returns a list of objects. We find all the `jokes` as we have mentioned no condition inside of `findMany`. You can learn more about how to add conditions inside of `findMany` [here](https://github.com/prisma/prisma2/blob/master/docs/photon/api.md#findMany).

Below `Query`, paste `Mutation` as follows:

```js
const Mutation = mutationType({
  definition(t) {
    t.crud.createOneJoke({ alias: 'createJoke' })
    t.crud.deleteOneJoke({ alias: 'deleteJoke' })
  },
})
```

`Mutation` uses `mutationType` from the `nexus` package.

The CRUD API here exposes `createOneJoke` and `deleteOneJoke`.

`createOneJoke`, as the name suggests, creates a joke whereas `deleteOneJoke` deletes a joke.

`createOneJoke` is aliased as `createJoke` so while calling the mutation we call `createJoke` rather than calling `createOneJoke`.

Similarly, we call `deleteJoke` instead of `deleteOneJoke`.

Finally, put the following code below `Mutation`:

```js
const photon = new Photon()

new GraphQLServer({
  schema: makeSchema({
    types: [Query, Mutation, Joke],
    plugins: [nexusPrismaPlugin()],
  }),
  context: { photon },
}).start(() =>
  console.log(
    `🚀 Server ready at: http://localhost:4000\n⭐️ See sample queries: http://pris.ly/e/js/graphql#5-using-the-graphql-api`,
  ),
)

module.exports = { Joke }
```

We use the `makeSchema` method from the `nexus` package to combine our model `Quote`, add `Query` and `Mutation` to the `types` array. We also add `nexusPrismaPlugin` to our `plugins` array. Finally, we start our server at [http://localhost:4000/](http://localhost:4000/). Port 4000 is the default port for [graphql-yoga](https://github.com/prisma-labs/graphql-yoga). You can change the port as suggested [here](https://github.com/prisma/prisma2/issues/594#issuecomment-534545903).

Let's start the server now. But first, we need to make sure our latest schema changes are written to the `node_modules/@generated/photon` directory. This happens when you run `prisma2 generate`. After that, we need to migrate our database to create tables.

### Migrate your database with Lift

Migrating your database with Lift follows a 2-step process:

1. Save a new migration (migrations are represented as directories on the file system)
2. Run the migration (to migrate the schema of the underlying database)

In CLI commands, these steps can be performed as follows (the CLI steps are in the process of being updated to match):

```bash
$ prisma2 lift save --name 'init'
$ prisma2 lift up
```

Now the migration process is done. We've successfully created the table. Now we can seed our database with initial values.

Go ahead and run the following command in the terminal:

```bash
$ yarn seed
```

This will seed our database with 8 habits as specified in our `seed.js` file.

Now you can run the server by typing:

```bash
$ yarn dev
```

This will run your server at [http://localhost:4000/](http://localhost:4000/) which you can open and query all the APIs you've made.

### List all jokes

```graphql
query jokes {
  jokes {
    id
    joke
  }
}
```

![Dad Jokes - List All Jokes GraphiQL](https://imgur.com/SDHCU1U.png)


### Find A Particular Joke

```graphql
query joke {
  joke(
    where: {
      joke: "Did you hear the one about the guy with the broken hearing aid? Neither did he."
    }
  ) {
    id
    joke
  }
}
```

![Dad Jokes - Find A Particular Joke GraphiQL](https://imgur.com/6fye7oj.png)

### Create A Joke

```graphql
mutation createJoke {
  createJoke(
    data: { joke: "To the guy who invented zero... thanks for nothing." }
  ) {
    id
    joke
  }
}
```

![Dad Jokes - Create A Joke GraphiQL](https:/imgur.com/1V9hWYS.png)

### Delete a joke

```graphql
mutation deleteJoke {
  deleteJoke(where: { id: "ck2zqhwvo0001cav551f1me34" }) {
    id
    joke
  }
}
```

![Dad Jokes - Delete A Joke GraphiQL](https://imgur.com/qaPg3JA.png)

This is all we need for the backend. Let's work on the frontend now.

## Client-Side (React Native)

### Bootstrap a new Expo project

Let’s set up a new Expo project using `expo-cli`. Firstly, make sure to install it globally and then run the `init` command:

```bash
$ yarn global add expo-cli
$ expo init DadJokes
```

Select the following in the interactive prompts:

1. Select **tabs**
2. Type name of the project to be **DadJokes**
3. Press **y** to install dependencies with **yarn**

This should bootstrap a new React Native project using `expo-cli`.

Now run the project by typing:

```bash
$ yarn start
```

Press `i` to run the iOS Simulator. This will automatically run the iOS Simulator even if it’s not opened.

Press `a` to run the Android Emulator. Note that the emulator must be installed and started already before typing `a`. Otherwise, it will throw an error in the terminal.

It should look like this:

![Dad Jokes - Expo Init](https://imgur.com/aPBEMZ6.png)

### React Navigation

The initial setup has already installed `react-navigation` for us. The bottom tab navigation also works by default because we chose `tabs` in the second step of `expo init`. You can check it by tapping on **Links** and **Settings**.

The `screens/` folder is responsible for the content displayed when the tabs are changed.

Now, completely remove the contents of `HomeScreen.js` and replace them with the following:

```js
import React from 'react'
import { Text, View } from 'react-native'

class HomeScreen extends React.Component {
  render() {
    return (
      <View>
        <Text>Home Screen</Text>
      </View>
    )
  }
}

export default HomeScreen
```

Now we’ll adapt the tabs according to the application we’re going to build. For our Dad Jokes app, we’re going to have 2 screens: `Home` and `Add Joke`.

We can completely delete `LinksScreen.js` and `SettingsScreen.js` from the `screens/` folder. Notice our app breaks, with a red screen full of errors.

This is because we’ve linked to it in the `navigation/` folder. Open `MainTabNavigator.js` in the `navigation/` folder. It currently looks like this:

```js
import React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';

import TabBarIcon from '../components/TabBarIcon';
import HomeScreen from '../screens/HomeScreen';
import LinksScreen from '../screens/LinksScreen';
import SettingsScreen from '../screens/SettingsScreen';

const config = Platform.select({
  web: { headerMode: 'screen' },
  default: {},
});

const HomeStack = createStackNavigator(
  {
    Home: HomeScreen,
  },
  config
);

HomeStack.navigationOptions = {
  tabBarLabel: 'Home',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon
      focused={focused}
      name={
        Platform.OS === 'ios'
          ? `ios-information-circle${focused ? '' : '-outline'}`
          : 'md-information-circle'
      }
    />
  ),
};

HomeStack.path = '';

const LinksStack = createStackNavigator(
  {
    Links: LinksScreen,
  },
  config
);

LinksStack.navigationOptions = {
  tabBarLabel: 'Links',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-link' : 'md-link'} />
  ),
};

LinksStack.path = '';

const SettingsStack = createStackNavigator(
  {
    Settings: SettingsScreen,
  },
  config
);

SettingsStack.navigationOptions = {
  tabBarLabel: 'Settings',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'} />
  ),
};

SettingsStack.path = '';

const tabNavigator = createBottomTabNavigator({
  HomeStack,
  LinksStack,
  SettingsStack,
});

tabNavigator.path = '';

export default tabNavigator;
```

Remove references to `LinksStack` and `SettingsStack` completely, because we don’t need these screens in our app. It should look like this:

```js
import React from 'react'
import { Platform } from 'react-native'
import {
  createBottomTabNavigator,
  createStackNavigator,
} from 'react-navigation'
import TabBarIcon from '../components/TabBarIcon'
import HomeScreen from '../screens/HomeScreen'

const HomeStack = createStackNavigator({
  Home: HomeScreen,
})

HomeStack.navigationOptions = {
  tabBarLabel: 'Home',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon
      focused={focused}
      name={
        Platform.OS === 'ios'
          ? `ios-information-circle${focused ? '' : '-outline'}`
          : 'md-information-circle'
      }
    />
  ),
}

export default createBottomTabNavigator({
  HomeStack,
})
```

Now reload the app to see the error gone.

Go ahead and create `AddJokeScreen.js` inside the `screens/` folder.

Add the following inside `AddJokeScreen.js`:

```js
import React from 'react'
import { Text, View } from 'react-native'

class AddJokeScreen extends React.Component {
  render() {
    return (
      <View>
        <Text>Add Joke Screen</Text>
      </View>
    )
  }
}

export default AddJokeScreen
```

Open up `MainTabNavigator.js` and import `AddJokeScreen` at the top:

```js
import AddJokeScreen from '../screens/AddJokeScreen'
```

Now go ahead and add the following code above our default export:

```js
const AddJokeStack = createStackNavigator({
  AddJoke: AddJokeScreen
})

AddJokeStack.navigationOptions = {
  tabBarLabel: 'Add Joke',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon
      focused={focused}
      name={
        Platform.OS === 'ios'
          ? `ios-add-circle${focused ? '' : '-outline'}`
          : 'md-add-circle'
      }
    />
  )
}
```

Also, change the default export to:

```js
export default createBottomTabNavigator({
  HomeStack,
  AddJokeStack
})
```

Now you should see 2 screens: `Home` and `AddJoke` with their respective icons as follows:

![Dad Jokes - Bottom Tab Navigation Icons](https://imgur.com/dKup1H1.png)

We now need to get rid of the header that’s showing on each screen, taking up some top space. To get rid of it, we need to add `headerMode: 'none'` in the `createStackNavigator` config.

We need to add it to `HomeStack` and `AddJokeStack`.

`HomeStack` should become:

```js
const HomeStack = createStackNavigator(
  { Home: HomeScreen },
  { headerMode: 'none' }
)
```

`AddJokeStack` should become:

```js
const AddJokeStack = createStackNavigator(
  { AddJoke: AddJokeScreen },
  { headerMode: 'none' }
)
```

Now if you check the text goes up to the top-left right above the clock.

![Dad Jokes - Text Above Right Clock](https://imgur.com/I1c16Qh.png)

There’s an easy fix for this. We need to use `SafeAreaView`. `SafeAreaView` renders content within the safe area boundaries of a device. Let’s go into the `screens/` directory and change `HomeScreen.js` to use `SafeAreaView` so that it looks like this:

```js
import React from 'react'
import { SafeAreaView, Text } from 'react-native'

class HomeScreen extends React.Component {
  render() {
    return (
      <SafeAreaView>
        <Text>Home Screen</Text>
      </SafeAreaView>
    )
  }
}

export default HomeScreen
```

It now renders the content inside the boundaries of the device.

![Dad Jokes - SafeAreaView](https://imgur.com/CYGrzFv.png)

Also, do it for `AddJokeScreen` like so:

```js
import React from 'react'
import { SafeAreaView, Text } from 'react-native'

class AddJokeScreen extends React.Component {
  render() {
    return (
      <SafeAreaView>
        <Text>Add Joke Screen</Text>
      </SafeAreaView>
    )
  }
}

export default AddJokeScreen
```

It’s repetitive to wrap `SafeAreaView` inside every component instead of setting it up on a root component like `App.js`. But be aware that this won’t work if you try doing it on `App.js`.

Remember, `SafeAreaView` should always be set up on screen components or any content in them, and not wrap entire navigators. You can read more about it on [this blog post](https://reactnavigation.org/docs/en/handling-iphonex.html).

### GraphQL Queries and Mutations

Lets add GraphQL queries to our app which we triggered through the GraphiQL editor.

Inside `components` folder, create a `graphql` folder.

```bash
$ mkdir graphql && cd $_
```

Inside `graphql` folder, create `mutations` and `queries` folder.

```bash
$ mkdir mutations queries
```

Inside `queries` folder, create a file named `jokes.js`.

```bash
$ cd queries
$ touch jokes.js
```

Inside `jokes.js`, paste the following:

```js
import { gql } from 'apollo-boost'

export const LIST_ALL_JOKES_QUERY = gql`
  query jokes {
    jokes {
      id
      joke
    }
  }
`
```

Notice that the above `query` is similar to what we typed in the GraphiQL editor. This is how GraphQL is used. First, we type the query in the GraphiQL editor and see if it gives the data that we need and then we just copy-paste it into the application.

Inside `mutations` folder, create 2 files `createJoke.js` and `deleteJoke.js`.

```bash
$ cd ../mutations
$ touch createJoke.js deleteJoke.js
```

Inside `createJoke.js`, paste the following:

```js
import { gql } from 'apollo-boost'

export const CREATE_JOKE_MUTATION = gql`
  mutation createJoke($joke: String!) {
    createJoke(data: { joke: $joke }) {
      id
      joke
    }
  }
`
```

Again we have copied the `mutation` from our GraphiQL editor above. The main difference is we have replaced the hardcoded value with a variable so we can type in whatever user has specified.

Inside `deleteJoke.js`, paste the following:

```js
import { gql } from 'apollo-boost'

export const DELETE_JOKE_MUTATION = gql`
  mutation deleteJoke($id: ID) {
    deleteJoke(where: { id: $id }) {
      id
      joke
    }
  }
`
```

Now create 2 files in `components/` folder namely `Error.js` and `Loading.js`.

```bash
$ cd ../../
$ touch Loading.js Error.js
```

In `Error.js`, paste the following:

```js
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { Text } from 'react-native-elements'

export const Error = () => (
  <View>
    <Text h3 h3Style={styles.error}>
      Sorry, looks like we've run into an error
    </Text>
  </View>
)

const styles = StyleSheet.create({
  error: {
    color: 'red'
  }
})
```

In `Loading.js`, paste the following:

```js
import React from 'react'
import { ActivityIndicator } from 'react-native'

export const Loading = () => <ActivityIndicator size='small' />
```

These components will be used later in the application.

### Screens

Now that our navigation is taken care of, we can start working on the layout.

We’re going to be using a UI toolkit called [React Native Elements](https://react-native-training.github.io/react-native-elements/). We will also be using [Apollo Client](https://apollographql.com/docs/react) to connect to our Prisma GraphQL backend.


So go ahead and install them:

```bash
$ yarn add react-native-elements @apollo/react-hooks apollo-boost graphql
```

Now open up `App.js` and connect our client to the backend.

First, import the following:

```js
import { ApolloProvider } from '@apollo/react-hooks'
import ApolloClient from 'apollo-boost'
```

Then right below it, create a constant:

```js
const client = new ApolloClient({
  uri: 'http://localhost:4000/'
})
```

The `uri` inside of `ApolloClient` is pointing out to Prisma GraphQL backend.

Then in the return wrap `AppNavigator` with `ApolloProvider` and pass in the `client`:

```js
<ApolloProvider client={client}>
  <AppNavigator />
</ApolloProvider>
```

Now anything that will be inside of `AppNavigator` can use Apollo Hooks.

Make sure your whole `App.js` file looks like:

```js
import { ApolloProvider } from '@apollo/react-hooks'
import { Ionicons } from '@expo/vector-icons'
import ApolloClient from 'apollo-boost'
import { AppLoading } from 'expo'
import { Asset } from 'expo-asset'
import * as Font from 'expo-font'
import React, { useState } from 'react'
import { Platform, StatusBar, StyleSheet, View } from 'react-native'
import AppNavigator from './navigation/AppNavigator'

const client = new ApolloClient({
  uri: 'http://localhost:4000/'
})

export default function App(props) {
  const [isLoadingComplete, setLoadingComplete] = useState(false)

  if (!isLoadingComplete && !props.skipLoadingScreen) {
    return (
      <AppLoading
        startAsync={loadResourcesAsync}
        onError={handleLoadingError}
        onFinish={() => handleFinishLoading(setLoadingComplete)}
      />
    )
  } else {
    return (
      <View style={styles.container}>
        {Platform.OS === 'ios' && <StatusBar barStyle='default' />}
        <ApolloProvider client={client}>
          <AppNavigator />
        </ApolloProvider>
      </View>
    )
  }
}

async function loadResourcesAsync() {
  await Promise.all([
    Asset.loadAsync([
      require('./assets/images/robot-dev.png'),
      require('./assets/images/robot-prod.png')
    ]),
    Font.loadAsync({
      // This is the font that we are using for our tab bar
      ...Ionicons.font,
      // We include SpaceMono because we use it in HomeScreen.js. Feel free to
      // remove this if you are not using it in your app
      'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf')
    })
  ])
}

function handleLoadingError(error) {
  // In this case, you might want to report the error to your error reporting
  // service, for example Sentry
  console.warn(error)
}

function handleFinishLoading(setLoadingComplete) {
  setLoadingComplete(true)
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  }
})
```

Now we’ll start working on the Home screen.

#### Home Screen

Before starting to work on `HomeScreen.js`, let’s delete unnecessary files. Go to the `components/` folder and delete `StyledText.js` and the `__tests__` folder.

Open up `HomeScreen.js` and paste the following:

```js
import React from 'react'
import { SafeAreaView, StyleSheet } from 'react-native'
import { Text } from 'react-native-elements'
import { ListJokes } from '../components/ListJokes'

class HomeScreen extends React.Component {
  render() {
    return (
      <SafeAreaView>
        <Text h1 h1Style={styles.h1}>
          Dad Jokes
        </Text>
        <ListJokes />
      </SafeAreaView>
    )
  }
}

const styles = StyleSheet.create({
  h1: {
    textAlign: 'center'
  }
})

export default HomeScreen
```

Create a new file inside the `components/` folder called `ListJokes.js` and paste the following in it:

```js
import { useMutation, useQuery } from '@apollo/react-hooks'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { ListItem, Text } from 'react-native-elements'
import { Error } from './Error'
import { DELETE_JOKE_MUTATION } from './graphql/mutations/deleteJoke'
import { LIST_ALL_JOKES_QUERY } from './graphql/queries/jokes'
import { Loading } from './Loading'

const removeJoke = (id, deleteJoke) => {
  deleteJoke({
    variables: {
      id
    },
    update: (cache, { data }) => {
      const { jokes } = cache.readQuery({
        query: LIST_ALL_JOKES_QUERY
      })
      cache.writeQuery({
        query: LIST_ALL_JOKES_QUERY,
        data: {
          jokes: jokes.filter(joke => joke.id !== id)
        }
      })
    }
  })
}

export const ListJokes = () => {
  const { loading, error, data } = useQuery(LIST_ALL_JOKES_QUERY)
  const [deleteJoke] = useMutation(DELETE_JOKE_MUTATION)

  if (loading) return <Loading />

  if (error) return <Error />

  const jokes = data.jokes
  return (
    <View style={styles.container}>
      {!jokes.length ? (
        <Text h4 h4Style={styles.center}>
          No jokes in the database. Add one :)
        </Text>
      ) : (
        jokes.map((item, i) => (
          <ListItem
            key={i}
            title={item.joke}
            bottomDivider
            rightIcon={{
              name: 'delete',
              onPress: () => removeJoke(item.id, deleteJoke)
            }}
          />
        ))
      )}
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    margin: 10
  },
  center: {
    textAlign: 'center',
    color: 'red'
  }
})
```

Here, we use the `useQuery` API from `@apollo/react-hooks`. We pass in `LIST_ALL_JOKES_QUERY` to it. And we get back 3 parameters, `loading`, `error` and `data`.

We show `<Loading />` component if `loading` is true.

We show `<Error />` component if `error` is true.

Then if we don't have jokes we display a friendly message `No jokes in the database. Add one :)`.

![Dad Jokes - No Jokes](https://imgur.com/MkGIWJz.png)

If we do have jokes in the database then we display the jokes.

![Dad Jokes - List Jokes](https://imgur.com/VPPJyO7.png)

We use `ListItem` to render the jokes.

We specify a `delete` icon in the `rightIcon` parameter of `ListItem` and `onPress` it calls `removeJoke` function.

We pass in `deleteJoke` function to `removeJoke` function. This `deleteJoke` function we get when we call `useMutation` with `DELETE_JOKE_MUTATION`. When this function is called with an appropriate `joke.id`, it deletes the joke from the database.

Later, we update the cache to filter it from our local cache. This optimistically updates the UI to remove deleted results from the UI without having to refresh the app.

#### Add Joke Screen

Open up `AddJokeScreen.js` and paste the following:

```js
import React from 'react'
import { SafeAreaView, StyleSheet } from 'react-native'
import { Text } from 'react-native-elements'
import { CreateJoke } from '../components/CreateJoke'

class HomeScreen extends React.Component {
  render() {
    return (
      <SafeAreaView>
        <Text h1 h1Style={styles.h1}>
          Add Joke
        </Text>
        <CreateJoke />
      </SafeAreaView>
    )
  }
}

const styles = StyleSheet.create({
  h1: {
    textAlign: 'center'
  }
})

export default HomeScreen
```

Now lets create a new file called `CreateJoke.js` in the `components/` folder and paste the following in it:

```js
import { useMutation } from '@apollo/react-hooks'
import React, { useState } from 'react'
import { Alert, StyleSheet, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import { Error } from './Error'
import { CREATE_JOKE_MUTATION } from './graphql/mutations/createJoke'
import { LIST_ALL_JOKES_QUERY } from './graphql/queries/jokes'

const saveJoke = (joke, changeJoke, createJoke) => {
  if (joke.trim() === '') {
    return
  }
  createJoke({
    variables: { joke },
    update: (cache, { data }) => {
      const { jokes } = cache.readQuery({
        query: LIST_ALL_JOKES_QUERY
      })

      cache.writeQuery({
        query: LIST_ALL_JOKES_QUERY,
        data: {
          jokes: jokes.concat(data.createJoke)
        }
      })
    }
  })
  Alert.alert('Joke added to the database')
  changeJoke('')
}

export const CreateJoke = () => {
  const [joke, changeJoke] = useState('')
  const [createJoke, { error, data }] = useMutation(CREATE_JOKE_MUTATION)
  if (error) {
    return <Error />
  }
  return (
    <View style={styles.wrapper}>
      <Input
        placeholder='Enter the joke'
        value={joke}
        onChangeText={changeJoke}
      />
      <Button
        type='outline'
        title='Save Joke'
        onPress={() => saveJoke(joke, changeJoke, createJoke)}
        containerStyle={styles.button}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  wrapper: {
    margin: 8
  },
  button: {
    marginTop: 16,
    padding: 4
  }
})
```

It should look like:

![Dad Jokes - Add Joke](https://imgur.com/HSVoAHz.png)

Here, we simply add an `Input` from `react-native-elements` to enter the joke. Then we have `Button` which when submitted calls `saveQuote` with 3 parameters, namely, `joke`, `changeJoke` and `createJoke`. We get `createJoke` by calling in `useMutation` with `CREATE_JOKE_MUTATION`.

In `saveQuote` function, we call in `createJoke` with `joke` variable. This creates a joke in the database. Then we optimistically update the UI to add the new joke to the list so we don't have to refresh the app to see the results.

Later, we throw an `Alert` that the joke has been added and then we clear the `Input` by calling in `changeJoke` with empty string `''`.

![Dad Jokes - With Alert](https://imgur.com/6t79cH2.png)

## Conclusion

In this tutorial, we built a Dad Jokes app with The Prisma Framework and React Native, totally inspired by [icanhazdadjoke](https://icanhazdadjoke.com/). You can find the complete code available [here on Github](https://github.com/deadcoder0904/DadJokes).

The Prisma Framework (formerly, Prisma 2) allows us to write a query in our language of choice and then it maps everything out to a database so we don't have to worry about writing it in the database language. We can easily swap out any database by using it. Right now, it only supports SQLite, mySQL, and PostgreSQL but soon other databases will be supported when it comes out of [beta](https://isprisma2ready.com/).

Give it a shot and I'm sure you'll like the experience.