CRUD (Create Read Update Delete) / AWS Amplify + GraphQL + React Native
Tags: reactnative, aws, awsamplify, graphql
Published: 2019-12-10 19:43:09

This part II is a logical continuation of part I, the series Full Stack Serverless.
At the end of this article, we will make this mobile application:

![mobileapp](https://miro.medium.com/max/5200/1*cRS3dwVPpTRAMwUUeuc1HQ.png)

The final code for this part can be found on [Github](https://github.com/react-native-village/aws-amplify-react-hooks/tree/Part2/examples/reactNativeCRUD).

#Introduction
Creating a backend on AWS Amplify is working with serverless technology, so before proceeding with coding, we will figure out what serverless computing is and what are their advantages over server computing.

Prediction of pundits from Berkeley University about how the backend technology will develop:

>By providing a simplified programming environment, serverless computing makes the cloud much easier to use, thereby attracting more people who can and will use it. Serverless computing comprises FaaS and BaaS offerings, and marks an important maturation of cloud programming. It obviates the need for manual resource management and optimization that today’s serverful computing imposes on application developers, a maturation akin to the move from assembly language to high-level languages more than four decades ago.

>We predict that serverless use will skyrocket. We also project that hybrid cloud on-premises applications will dwindle over time, though some deployments might persist due to regulatory constraints and data governance rules.

>Serverless computing will become the default computing paradigm of the Cloud Era, largely replacing serverful computing and thereby bringing closure to the Client-Server Era.

>[Cloud Programming Simplified: A Berkeley View on Serverless Computing](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2019/EECS-2019-3.pdf)

Serverless computing is a natural cloud architecture that transfers most of AWS's operational responsibility, and thus provides more flexibility and innovative capabilities. Serverless computing allows you to create and run applications and services without worrying about servers. They eliminate the need to deal with infrastructure management issues, such as allocating servers or clusters, necessary resources, as well as installing patches and maintaining the operating system. They can be used for almost any type of application or server-side services, while everything that is required to run and scale an application with high availability is performed without client intervention.


#Backend - Create an API

We will now create the GraphQL API, which interacts with the DynamoDB NoSQL database to perform CRUD operations (create, read, update, delete).

```bash
amplify add api 
```


![addapi](https://miro.medium.com/max/2994/1*KXb3-t8wyjjHfO1OuLH44g.png)



After the selected items, a diagram will open, which is always available for editing at ./amplify/backend/api/messaga/schema.graphql
Where we add the following code:


```javascript
type Job 
  @model
  @auth(
    rules: [
      {allow: owner, ownerField: "owner", operations: [create, update, delete]},
    ])
{
  id: ID!
  position: String!
  rate: Int!
  description: String!
  owner: String
}
```

This is a GraphQL schema. GraphQL Transform provides an easy-to-use abstraction that helps you quickly create server parts for web and mobile applications in AWS. Using GraphQL Transform, you define the data model of your application using the GraphQL Schema Definition Language (SDL), and the library handles the conversion of the SDL definition to a set of fully descriptive AWS CloudFormation templates that implement your data model.

When used with tools like the Amplify CLI, GraphQL Transform simplifies the process of developing, deploying, and supporting GraphQL APIs. With it, you define your API using the GraphQL Schema Definition Language (SDL) and then use automation to transform it into a fully descriptive cloud information template that implements the specification.

GraphQL is an API specification. This is the query language for the API and the runtime to execute these queries with your data. It has some similarities with REST and is the best replacement for REST.

GraphQL was introduced by Facebook in 2015, although it has been used internally since 2012. GraphQL allows clients to determine the structure of the required data, and it is this structure that is returned from the server. Querying data in this way provides a much more efficient way for client-side applications to interact with APIs, reducing the number of incomplete samples and preventing excessive data samples.

[Learn more about the benefits of GraphQL here.](https://www.altexsoft.com/blog/engineering/graphql-core-features-architecture-pros-and-cons/)

Let us return to our scheme, where the main components of the GraphQL scheme are object types, which simply represent the type of object that you can extract from your service, and what fields it has.

`Job` is a type of a GraphQL object (GraphQL Object Type), that is, a type with some fields. Most types in your schema will be object types.

`id` position rate description owner - fields in type Job. This means that these are the only fields that can appear in any part of a GraphQL query that works with the Job type.

`String` is one of the built-in scalar types - these are types that are resolved into a single scalar object and cannot have subselects in the query. We will look at scalar types later.

`String!` - the field is not NULL, means that the GraphQL service promises to always give you a value when requesting this field. In general, this is a required field.

GraphQL comes with a set of default scalar types out of the box:

`Int` 32-bit signed integer.

`Float` double-precision floating point value.

`String` character sequence UTF - 8.

`Boolean` true or false.

`ID` scalar type ID is a unique identifier often used to retrieve an object or as a key for a cache. The type of identifier is serialized in the same way as a string; however, the definition of it as an identifier means that it is not intended for human perception.


##Directives
`@model` — Object types marked with @model are top level objects in the generated API. Objects marked with @model are stored in Amazon DynamoDB and can be protected with @auth, linked to other objects via @connection

`@auth` - Authorization is required for applications to interact with your GraphQL API. API keys are best used for public APIs.
The @auth object types annotated by @auth are protected by a set of authorization rules that provide you with additional controls than top-level authorization in the API. You can use the @ auth directive to determine the types of objects and fields in your project schema.
When using the @ auth directive for object type definitions, which are also annotated by @model, all recognition tools that return objects of this type will be protected.

Other directives and details in official [documentation](https://aws-amplify.github.io/docs/cli-toolchain/graphql?sdk=js#directives).

###`@auth` directive rules

```javascript
@auth( rules: [ {allow: owner, ownerField: "owner", operations: [create, update, delete]} ])
```

mean that the operations CREATE, UPDATE, DELETE are allowed exclusively to the owner, and the read operation is for everyone.

It's time to test it in practice! Therefore, we write the command in the console:

```bash
amplify mock api
```

![mockapi](https://miro.medium.com/max/3124/1*fs-GeUIaPGVP3-5Jwi3m2Q.png)

With this team, you can quickly test your achievements of change without the need to allocate or update the cloud resources that you use at each stage. In this way, you can configure unit and integration tests that can be performed quickly without affecting your cloud backend.


#Three whales on which GraphQL stands:

##Query (READ)

Simply put, queries in GraphQL are how you intend to query data. You will receive exactly the data that you need. No more no less.

##Mutation (CREATE UPDATE DELETE)

Mutations in GraphQL are a way to change data on a server and get updated data back.

##Subscriptions

A way to maintain a connection to the server in real time. This means that whenever an event occurs on the server and when this event is called, the server will send the appropriate data to the client.

You can see all available methods by clicking on Docs (Documentation Explorer) in the upper right corner. The values are clickable, so you can see all the possible queries.

![docs](https://miro.medium.com/max/5760/1*wcYg4qmPXOdghWgS5P1RtQ.png)


##CREATE

We open our API at the address that issued (each has its own) the result of the `amplify mock api` command

![mockapi](https://miro.medium.com/max/2024/1*Y3A3QEl0B-QvJReQL1c5DQ.png)

and execute the CREATE query by pressing the play button.

```javascript
mutation Create {
  __typename
  createJob(input: {position: "React Native Developer", rate: 3000, description: "We are looking for a React Native developer (St. Petersburg) to develop from scratch a mobile version of the main cs.money platform  Our product is an international trading platform for the exchange of virtual items. (CS: GO, Dota 2) which is shared by more than 5 million users. The project has existed for more than 3 years. and takes a leading position in its field. The platform is used in more than 100 countries of the world.  Now we want to make a mobile application and decided to do it on React Native. You have to develop an application from scratch and this is a great opportunity to build a good architecture without resting on legacy.  Requirements:  Knowledge of react patterns Knowledge of SOLID principles Knowledge of the basics of mobile specifics (caching, working with the native API, rendering optimization) Knowledge of RN"}) {
    description
    id
    owner
    position
    rate
  }
}
```

![create](https://miro.medium.com/max/5664/1*GwJb9ZZVB6px_QI8xKwslQ.png)

To consolidate the material, create some more vacancies.

##READ

We get a list of all the vacancies. Insert request:

```javascript
query Read {
  __typename
  listJobs {
    items {
      description
      id
      owner
      position
      rate
    }
  }
}
```

![read](https://miro.medium.com/max/5760/1*ykTq1YUoBLn6G45fQ3eLXQ.png)

##UPDATE

To update, we need to take the vacancy `ID` (be sure to enter your own, and not from the example) and pass it to this request with the data changed. For example, update the `position` and `rate` fields

```javascript
mutation Update {
  __typename
  updateJob(input: {id: "1a8a763f-28b8-450a-96f0-73e0d1d8ac04", position: "Full Stack Serverless", rate: 5000}) {
    id
    description
    owner
    position
    rate
  }
}
```

![update](https://miro.medium.com/max/5760/1*IxfySd5he-V-lxJSyTsqNA.png)

##DELETE

To delete, as well as in the case of the update, we need to transfer the vacancy `ID` (be sure to enter your own, and not from the example).

```javascript
mutation Delete {
  __typename
  deleteJob(input: {id: "1a8a763f-28b8-450a-96f0-73e0d1d8ac04"}) {
    id
  }
}
```

![delete](https://miro.medium.com/max/5760/1*vqFyPU7VVMmulZeF_zd0mg.png)


#Permissions
Now let's check if our rules work, which we indicated in the scheme. Only the owner can update, delete and create.

```javascript
@auth( rules: [ {allow: owner, ownerField: “owner”, operations: [create, update, delete]} ])
```

To change the user, click on UpdateAuth in the main menu. Where we randomly update Username and Email.

![updateauth](https://miro.medium.com/max/430/1*a89USnLuvHC0OXf3wdgYSA.png)

![authoptions](https://miro.medium.com/max/3588/1*BP6B8Wwe9Ldgugf2lUyyPA.png)

If we send a READ request, then it works, but if we send an UPDATE or DELETE request and we get an error.

![error](https://miro.medium.com/max/5760/1*NevLJXxyURK4JOx8qWyy4w.png)

The rules work, as required!

Now that we have tested the functionality of the API, we can publish it to the cloud with the command:

```bash
amplify push
```

![push](https://miro.medium.com/max/2116/1*UD4BuMSKZjod6uehROB3BQ.png)

Or immediately proceed to create a mobile interface to the created API in the React Native application.




#FRONTEND

#Preparation

To implement this workflow, we need the following libraries:
[react-navigation-tabs](https://github.com/react-navigation/tabs#readme)
[@aws-amplify/api](https://github.com/aws-amplify/amplify-js)
[@aws-amplify/pubsub](https://github.com/aws-amplify/amplify-js)
[aws-amplify-react-hooks](https://github.com/react-native-village/aws-amplify-react-hooks)

We put them at once:

```bash
yarn add react-navigation-tabs @aws-amplify/api@1.2.4 @aws-amplify/core@1.2.4 @aws-amplify/pubsub@1.2.4 aws-amplify-react-hooks
```

#Creation and layout of components

##Card 
Create a Card Wrap Component
src/components/Card/index.js

```javascript
import React, { memo } from 'react'
import { StyleSheet, View } from 'react-native'
import { BLUE } from '../../constants'

const styles = StyleSheet.create({
  card: {
    borderRadius: 17,
    borderWidth: 0.5,
    borderColor: BLUE,
    padding: 25,
    marginBottom: 20
  }
})

const Card = memo(({ children }) => {
  const { card } = styles
  return <View style={card}>{children}</View>
})

export { Card }
```

##CardJob
Create the CardJob component
src/components/CardJob/index.js

![cardjob](https://miro.medium.com/max/2338/1*IOONyErLvJTBKHhSOTM2Lw.png)
```javascript
import React, { memo } from 'react'
import { StyleSheet, TouchableWithoutFeedback, Text, View } from 'react-native'
import { BLUE } from '../../constants'

const styles = StyleSheet.create({
  card: {
    borderRadius: 17,
    borderWidth: 0.5,
    borderColor: BLUE,
    padding: 25,
    height: 200,
    marginBottom: 20
  },
  h1: {
    fontFamily: '3270Narrow',
    color: '#dbdbdb',
    fontSize: 18
  },
  h2: {
    fontFamily: '3270Narrow',
    color: '#6a676a',
    marginVertical: 15,
    fontSize: 13,
    letterSpacing: 0.92,
    flexGrow: 1
  },
  footer: {
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  h3: {
    color: '#cfcfcf',
    fontFamily: '3270Narrow',
    textTransform: 'uppercase',
    fontSize: 18,
    letterSpacing: 0.97
  },
  h4: {
    color: '#cfcfcf',
    fontFamily: '3270Narrow',
    textTransform: 'uppercase',
    fontSize: 18,
    letterSpacing: 1.17
  }
})

const CardJob = memo(({ item: { position, description, rate, owner }, onPress }) => {
  const { card, h1, h2, footer, h3, h4 } = styles
  const userSlice = owner.slice(0, 10)
  return (
    <TouchableWithoutFeedback onPress={onPress}>
      <View style={card}>
        <Text style={h1}>{position}</Text>
        <Text style={h2} numberOfLines={5} ellipsizeMode="tail">
          {description}
        </Text>
        <View style={footer}>
          <Text style={h3}>{userSlice}</Text>
          <Text style={h4}>{rate}$</Text>
        </View>
      </View>
    </TouchableWithoutFeedback>
  )
})

export { CardJob }
```

##CardJobDetail 

Creating the CardJobDetail Component
src/components/CardJobDetail/index.js
![cardjobdetail](https://miro.medium.com/max/1296/1*l1e-M5QndLTwa9KjoG0cKQ.png)

```javascript
import React, { memo } from 'react'
import { StyleSheet, TouchableWithoutFeedback, Text, View } from 'react-native'
import { BLUE } from '../../constants'

const styles = StyleSheet.create({
  card: {
    borderRadius: 17,
    borderWidth: 0.5,
    borderColor: BLUE,
    padding: 25,
    marginBottom: 20
  },
  h1: {
    fontFamily: '3270Narrow',
    color: '#dbdbdb',
    fontSize: 18
  },
  h2: {
    fontFamily: '3270Narrow',
    color: '#6a676a',
    marginVertical: 15,
    fontSize: 13,
    letterSpacing: 0.92,
    flexGrow: 1
  },
  footer: {
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  h3: {
    color: '#cfcfcf',
    fontFamily: '3270Narrow',
    textTransform: 'uppercase',
    fontSize: 18,
    letterSpacing: 0.97
  },
  h4: {
    color: '#cfcfcf',
    fontFamily: '3270Narrow',
    textTransform: 'uppercase',
    fontSize: 18,
    letterSpacing: 1.17
  }
})

const CardJobDetail = memo(({ item: { position, description, rate, owner }, onPress }) => {
  const { card, h1, h2, footer, h3, h4 } = styles
  const userSlice = owner.slice(0, 10)
  return (
    <TouchableWithoutFeedback onPress={onPress}>
      <View style={card}>
        <Text style={h1}>{position}</Text>
        <Text style={h2}>{description}</Text>
        <View style={footer}>
          <Text style={h3}>{userSlice}</Text>
          <Text style={h4}>{rate}$</Text>
        </View>
      </View>
    </TouchableWithoutFeedback>
  )
})

export { CardJobDetail }
```

##InputMuliline 

Create InputMuliline
Edit src/screens/Authenticator/Form/index.js
![input](https://miro.medium.com/max/1124/1*TrVDvDTvvC-eMeJttfFkZg.png)

```diff
import { Platform } from 'react-native'
import t from 'tcomb-form-native'
import FloatingLabel from 'react-native-floating-label'
import {
  LABEL_COLOR,
  INPUT_COLOR,
  ERROR_COLOR,
  HELP_COLOR,
  BORDER_COLOR,
  DISABLED_COLOR,
  DISABLED_BACKGROUND_COLOR
} from '../../../constants'

const formValidation = {
  email: t.refinement(t.String, value => /@/.test(value)),
  password: t.refinement(t.String, value => value.length >= 8)
}

+export const structJob = t.struct({
+  position: t.refinement(t.String, value => value.length >= 3 && value <= 30),
+  rate: t.Number,
+  description: t.String
+})

export const structSignIn = t.struct({
  email: formValidation.email,
  password: formValidation.password
})

export const structSignUp = t.struct({
  email: formValidation.email,
  password: formValidation.password,
  passwordConfirmation: formValidation.password
})

export const structForgot = t.struct({
  email: formValidation.email
})

export const structForgotPass = t.struct({
  email: formValidation.email,
  code: t.Number,
  password: formValidation.password,
  passwordConfirmation: formValidation.password
})

export const structConfirmSignUp = t.struct({
  code: t.Number
})

const FONT_SIZE = 17
const FONT_WEIGHT = '500'

// Fixme
const Form = t.form.Form // eslint-disable-line

const formStyles = {
  ...Form.stylesheet,
  fieldset: {},
  // the style applied to the container of all inputs
  formGroup: {
    normal: {
      marginBottom: 8
    },
    error: {
      marginBottom: 8,
      marginLeft: 5,
      marginRight: 5
    }
  },
  controlLabel: {
    normal: {
      color: LABEL_COLOR,
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      marginLeft: 5,
      marginBottom: 7,
      fontWeight: FONT_WEIGHT
    },
    // the style applied when a validation error occours
    error: {
      marginLeft: 5,
      marginRight: 5,
      fontFamily: '3270Narrow',
      color: ERROR_COLOR,
      fontSize: FONT_SIZE,
      marginBottom: 7
    }
  },
  helpBlock: {
    normal: {
      color: HELP_COLOR,
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      marginBottom: 2
    },
    // the style applied when a validation error occours
    error: {
      marginLeft: 5,
      marginRight: 5,
      color: HELP_COLOR,
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      marginBottom: 2
    }
  },
  errorBlock: {
    fontFamily: '3270Narrow',
    fontSize: 12,
    marginBottom: 2,
    marginLeft: 5,
    marginRight: 5,
    color: ERROR_COLOR
  },
  textboxView: {
    normal: {},
    error: {},
    notEditable: {}
  },
  textbox: {
    normal: {
      color: INPUT_COLOR,
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      height: 50,
      paddingVertical: Platform.OS === 'ios' ? 7 : 0,
      paddingHorizontal: 12,
      borderRadius: 4,
      borderColor: BORDER_COLOR,
      borderWidth: 0.5,
      marginLeft: 5,
      marginRight: 5
    },
    // the style applied when a validation error occours
    error: {
      color: INPUT_COLOR,
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      height: 50,
      paddingVertical: Platform.OS === 'ios' ? 7 : 0,
      paddingHorizontal: 12,
      borderRadius: 4,
      borderColor: ERROR_COLOR,
      borderWidth: 0.5,
      marginBottom: 5
    },
    // the style applied when the textbox is not editable
    notEditable: {
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      height: 50,
      paddingVertical: Platform.OS === 'ios' ? 7 : 0,
      paddingHorizontal: 7,
      borderRadius: 4,
      borderColor: BORDER_COLOR,
      borderWidth: 0.5,
      marginBottom: 5,
      color: DISABLED_COLOR,
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  checkbox: {
    normal: {
      marginBottom: 4
    },
    // the style applied when a validation error occours
    error: {
      marginBottom: 4
    }
  },
  pickerContainer: {
    normal: {
      marginBottom: 4,
      borderRadius: 4,
      borderColor: BORDER_COLOR,
      borderWidth: 0.5
    },
    error: {
      marginBottom: 4,
      borderRadius: 4,
      borderColor: ERROR_COLOR,
      borderWidth: 0.5
    },
    open: {
      // Alter styles when select container is open
    }
  },
  select: {
    normal: Platform.select({
      android: {
        paddingLeft: 7,
        color: INPUT_COLOR
      },
      ios: {}
    }),
    // the style applied when a validation error occours
    error: Platform.select({
      android: {
        paddingLeft: 7,
        color: ERROR_COLOR
      },
      ios: {}
    })
  },
  pickerTouchable: {
    normal: {
      height: 44,
      flexDirection: 'row',
      alignItems: 'center'
    },
    error: {
      height: 44,
      flexDirection: 'row',
      alignItems: 'center'
    },
    active: {
      borderBottomWidth: 1,
      borderColor: BORDER_COLOR
    },
    notEditable: {
      height: 44,
      flexDirection: 'row',
      alignItems: 'center',
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  pickerValue: {
    normal: {
      fontFamily: '3270Narrow',
      fontSize: FONT_SIZE,
      paddingLeft: 7
    },
    error: {
      fontSize: FONT_SIZE,
      paddingLeft: 7
    }
  },
  datepicker: {
    normal: {
      marginBottom: 4
    },
    // the style applied when a validation error occours
    error: {
      marginBottom: 4
    }
  },
  dateTouchable: {
    normal: {},
    error: {},
    notEditable: {
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  dateValue: {
    normal: {
      color: INPUT_COLOR,
      fontSize: FONT_SIZE,
      padding: 7,
      marginBottom: 5
    },
    error: {
      color: ERROR_COLOR,
      fontSize: FONT_SIZE,
      padding: 7,
      marginBottom: 5
    }
  },
  buttonText: {
    fontFamily: '3270Narrow',
    fontSize: 18,
    color: 'white',
    alignSelf: 'center'
  },
  button: {
    height: 36,
    backgroundColor: INPUT_COLOR,
    borderColor: INPUT_COLOR,
    borderWidth: 0.5,
    borderRadius: 8,
    marginBottom: 10,
    alignSelf: 'stretch',
    justifyContent: 'center'
  }
}

export const options = {
  fields: {
    email: {
      stylesheet: formStyles,
      placeholder: 'Email',
      secureTextEntry: false,
      keyboardType: 'email-address',
      autoCapitalize: 'none',
      error: 'Without an email address how are you going to reset your password when you forget it?',
      factory: FloatingLabel
    },
    password: {
      stylesheet: formStyles,
      placeholder: 'Password',
      secureTextEntry: true,
      error: "Choose something you use on a dozen other sites or something you won't remember",
      factory: FloatingLabel
    },
    passwordConfirmation: {
      stylesheet: formStyles,
      placeholder: 'Confirm Password',
      secureTextEntry: true,
      error: "Choose something you use on a dozen other sites or something you won't remember",
      factory: FloatingLabel
    },
    code: {
      stylesheet: formStyles,
      placeholder: 'Verification code',
      secureTextEntry: false,
      keyboardType: 'numeric',
      error: 'Confident in their actions?',
      factory: FloatingLabel
    },
+    position: {
+      stylesheet: formStyles,
+      placeholder: 'Position',
+      secureTextEntry: false,
+      autoCapitalize: 'none',
+      error: 'Please enter position name',
+      factory: FloatingLabel
+    },
+    rate: {
+      stylesheet: formStyles,
+      placeholder: 'Rate',
+      secureTextEntry: false,
+      autoCapitalize: 'none',
+      keyboardType: 'numeric',
+      error: 'Please enter rate',
+      factory: FloatingLabel
+    },
+    description: {
+      stylesheet: {
+        ...Form.stylesheet,
+        controlLabel: {
+          normal: {
+            color: LABEL_COLOR,
+            fontFamily: '3270Narrow',
+            fontSize: FONT_SIZE,
+            marginLeft: 5,
+            marginBottom: 7,
+            justifyContent: 'center',
+            fontWeight: FONT_WEIGHT
+          },
+          error: {
+           color: ERROR_COLOR,
+            fontFamily: '3270Narrow',
+            fontSize: FONT_SIZE,
+            marginLeft: 5,
+            marginRight: 7,
+            fontWeight: FONT_WEIGHT
+          }
+        },
+        textbox: {
+          normal: {
+            height: 300,
+            color: INPUT_COLOR,
+            fontFamily: '3270Narrow',
+            fontSize: FONT_SIZE,
+            paddingHorizontal: 12,
+            borderRadius: 4,
+            textAlignVertical: 'top',
+            borderColor: BORDER_COLOR,
+            borderWidth: 0.5,
+            paddingTop: 13,
+            paddingBottom: 0,
+            marginLeft: 5,
+           marginRight: 5
+          },
+          error: {
+            ...Form.stylesheet.textbox.error,
+            height: 150
+          }
+        }
+      },
+      placeholder: 'Description',
+      secureTextEntry: false,
+      multiline: true,
+      autoCapitalize: 'none',
+      error: 'Please enter rate',
+      factory: FloatingLabel
+    }
  }
}
```

#AmplifyProvider

Essentially the same as [ApolloProvider] (https://www.apollographql.com/docs/react/essentials/get-started.html#creating-provider) for Apollo and [Provider](https://react-redux.js.org/introduction/quick-start#provider) for Redux.
Provides the ability to pass `Auth` authentication parameters, access to the `API`, and conducting `graphqlOperation` from anywhere in the application.

`API` - This is the GraphQL client that we will use to interact with the AppSync endpoint (analogue to fetch or axios)

`Auth` - This is a class from AWS Amplify that handles user management. You can use this class for everything from registering a user to resetting his password. In this component, we will call the Auth.user.attributes.sub method, which will return the current user ID

`graphqlOperation` - This is a JavaScript utility that parses GraphQL operations

In order to connect it. Editing the src/index.js file

```diff
import React from 'react'
import { StatusBar } from 'react-native'
import Amplify from '@aws-amplify/core'
+ import { Auth, API, graphqlOperation } from 'aws-amplify'
import * as Keychain from 'react-native-keychain'
import AppNavigator from './AppNavigator'
import awsconfig from '../aws-exports'
+ import { AmplifyProvider } from 'aws-amplify-react-hooks'

const MEMORY_KEY_PREFIX = '@MyStorage:'
let dataMemory = {}

+ const client = {
+   Auth,
+   API,
+   graphqlOperation
+ }

+ AmplifyProvider(client)

class MyStorage {
  static syncPromise = null

  static setItem(key, value) {
    Keychain.setGenericPassword(MEMORY_KEY_PREFIX + key, value)
    dataMemory[key] = value
    return dataMemory[key]
  }

  static getItem(key) {
    return Object.prototype.hasOwnProperty.call(dataMemory, key) ? dataMemory[key] : undefined
  }

  static removeItem(key) {
    Keychain.resetGenericPassword()
    return delete dataMemory[key]
  }

  static clear() {
    dataMemory = {}
    return dataMemory
  }
}

Amplify.configure({
  ...awsconfig,
  Analytics: {
    disabled: false
  },
  storage: MyStorage
})

const App = () => {
  return (
    <>
+     <AmplifyProvider client={client}>
        <StatusBar barStyle="dark-content" />
        <AppNavigator />
+      </AmplifyProvider>
    </>
  )
}

export default App
```

#Screens

##JobsMain
Create a JobsMain screen
src/screens/Jobs/JobsMain.js

![jobsmain](https://miro.medium.com/max/1036/1*vDoP8ekCHquDKy95Hdb0Gw.png)

On this screen, we will make a Query query, with the pagination option, where the number is through the useQuery hook and it will return an array to us, which we will send to Flatlist.

```javascript
import React from 'react'
import { FlatList } from 'react-native'
import { Auth } from 'aws-amplify'
import { useQuery, getNames } from 'aws-amplify-react-hooks'
import { listJobs } from '../../graphql/queries'
import { AppContainer, CardJob } from '../../components'
import { onScreen, BG } from '../../constants'
import { onCreateJob, onUpdateJob, onDeleteJob } from '../../graphql/subscriptions'

const JobsMain = ({ navigation }) => {
  const owner = Auth.user.attributes.sub

  const { data, loading, error, fetchMore } = useQuery(
    {
      listJobs,
      onCreateJob,
      onUpdateJob,
      onDeleteJob
    },
    {
      variables: { limit: 5 }
    },
    getNames({ listJobs, onCreateJob, onUpdateJob, onDeleteJob })
  )

  const _renderItem = ({ item }) => {
    const check = owner === item.owner
    return <CardJob item={item} onPress={onScreen(check ? 'JOB_ADD' : 'JOB_DETAIL', navigation, item)} />
  }

  const _keyExtractor = obj => obj.id.toString()

  return (
    <AppContainer
      flatlist
      message={error}
      loading={loading}
      iconRight="plus-a"
      colorLeft={BG}
      title=" "
      onPressRight={onScreen('JOB_ADD', navigation)}
    >
      <FlatList
        scrollEventThrottle={16}
        data={data}
        renderItem={_renderItem}
        keyExtractor={_keyExtractor}
        onEndReachedThreshold={0.5}
        onEndReached={fetchMore}
      />
    </AppContainer>
  )
}

export { JobsMain }
```


`Flatlist` - Productive interface for rendering lists
`listJobs` - GraphQL query operation to retrieve an array of data
`limit` - The number of elements in the pagination response.
`useQuery` - The hook to retrieve data per page.
`onCreateJob` `onUpdateJob` `onDeleteJob` - GraphQL query operations that implement real-time updating.
`getNames` - Function for obtaining names from generated Amplify queries

Next, we get the data through a hookQuery which has the following API:

##useQuery

```javascript
const { 
  data: Array<mixed>,
  loading: string,
  error: string,
  fetchMore: function
} = useQuery(query {}, options: { variables: {[key: string]: any }}, queryData: Array<string>)
```
  
`query` - The first argument is a GraphQL query READ operation, the second is a CREATE subscription operation, the third is an UPDATE subscription operation and the fourth is a DELETE subscription operation.

`option` - An object containing all the variables that your request should fulfill.

`queryData` - An array of GraphQL operation names in the READ, CREATE, UPDATE, DELETE sequence.

`data` — The returned data array.

`loading` - Loading indicator.

`error` - Error.

`fetchMore` - Often in your application there will be some views in which you need to display a list that contains too much data so that it can either be retrieved or displayed immediately. Pagination is the most common solution to this problem, and the useQuery hook has built-in functionality that makes it pretty simple. The easiest way to do pagination is to use the fetchMore function, which is included in the result object returned by the useQuery hook. This basically allows you to make a new GraphQL query and combine the result with the original result.


##JobsDetail 

Create a JobsDetail screen src/screens/Jobs/JobDetail.js

![jobsdetail](https://miro.medium.com/max/1068/1*kVZAm0RVF2nfDc6z2CY73A.png)

```javascript
import React from 'react'
import {AppContainer, CardJobDetail} from '../../components'
import {goBack} from '../../constants'

const JobDetail = ({navigation}) => (
   <AppContainer title = "" onPress = {goBack (navigation)}>
     <CardJobDetail item = {navigation.state.params} />
   </AppContainer>
)

export { JobDetail }
```

##JobAdd
 
Create a JobAdd screen
src/screens/Jobs/JobAdd.js

![jobadd](https://miro.medium.com/max/1056/1*jSgLBFxPmf4h1PF4R5SUiQ.png)

```javascript
import React, { useRef, useEffect, useState } from 'react'
import t from 'tcomb-form-native'
import { useMutation } from 'aws-amplify-react-hooks'
import { AppContainer, Card, Button, Space, TextLink } from '../../components'
import { structJob, options } from '../Authenticator/Form'
import { goBack, PINK } from '../../constants'
import { createJob, updateJob, deleteJob } from '../../graphql/mutations'

const Form = t.form.Form // eslint-disable-line

const JobAdd = ({ navigation }) => {
  const [check, setOwner] = useState(false)
  const [input, setJob] = useState({
    position: '',
    rate: '',
    description: ''
  })

  const onChange = item => setJob(item)

  useEffect(() => {
    const obj = navigation.state.params
    typeof obj !== 'undefined' && setOwner(true)
    setJob(obj)
  }, [navigation])

  const [setCreate, setUpdate, setDelete, { loading, error }] = useMutation(input)

  const onCreate = async () => (await setCreate(createJob)) && goBack(navigation)()
  const onUpdate = async () => (await setUpdate(updateJob)) && goBack(navigation)()
  const onDelete = async () => (await setDelete(deleteJob)) && goBack(navigation)()

  const registerForm = useRef('')
  return (
    <AppContainer loading={loading} message={error} title="Add" onPress={goBack(navigation)}>
      <Card>
        <Form ref={registerForm} type={structJob} options={options} value={input} onChange={text => onChange(text)} />
        <Space height={40} />
        <Button title="DONE" onPress={() => (check ? onUpdate() : onCreate())} />
        {check && (
          <>
            <TextLink title="or" />
            <Space height={10} />
            <Button title="DELETE" color={PINK} onPress={onDelete} />
          </>
        )}
        <Space />
      </Card>
    </AppContainer>
  )
}

export { JobAdd }

```

##useMutation

```javascript
const [
  setCreate: Promise<{}>,
  setUpdate: Promise<{}>,
  setDelete: Promise<{}>
{ 
  loading: string,
  error: string
}
] = useMutation(input: {})
```
  
`setCreate` `setUpdate` `setDelete` - Functions CREATE, UPDATE, DELETE

`loading` - Loading indicator.

`error` - Error.

`input` - Mutation value.


#Navigation

Connect screens in stack navigator
src/screens/Jobs/index.js

```javascript
import { createStackNavigator } from 'react-navigation-stack'
import { JobsMain } from './JobsMain'
import { JobDetail } from './JobDetail'
import { JobAdd } from './JobAdd'

const Jobs = createStackNavigator(
  {
    JOBS_MAIN: { screen: JobsMain },
    JOB_DETAIL: { screen: JobDetail },
    JOB_ADD: { screen: JobAdd }
  },
  {
    headerMode: 'none'
  }
)

Jobs.navigationOptions = ({ navigation }) => {
  let tabBarVisible = true

  if (navigation.state.index > 0) {
    tabBarVisible = false
  }

  return {
    tabBarVisible
  }
}

export { Jobs }
```

For the logic of navigation, we transfer the User component from Authenticator to the screens directory, where we edit the imports

```diff
-import { AppContainer, Button } from '../../../components'
-import { goHome } from '../../../constants'
+import { AppContainer, Button } from '../../components'
+import { goHome } from '../../constants'
```

and do not forget to remove the export from src/screens/Authenticator/index.js

```diff
export * from './Hello'
- export * from './User'
export * from './SignIn'
export * from './SignUp'
export * from './Forgot'
export * from './ForgotPassSubmit'
export * from './ConfirmSignUp'
```

Connect all created components to src/screens/index.js

```javascript
export * from './User'
export * from './Jobs'
```

##TabBar 

Create a TabBar
src/components/TabBar/index.js

```javascript
import React, { Component } from 'react'
import { View, StyleSheet, TouchableWithoutFeedback } from 'react-native'
import { ButtonTab } from './ButtonTab'
import { Device } from '../../constants'

const styles = StyleSheet.create({
  tabbar: {
    ...Device.select({
      iphone5: {
        height: 70
      },
      mi5: {
        height: 70
      },
      iphone678: {
        height: 100
      },
      googlePixel: {
        height: 90
      },
      redmiNote5: {
        height: 100
      }
    }),
    flexDirection: 'row',
    backgroundColor: '#0F0F0F',
    borderTopColor: '#0F0F0F',
    overflow: 'hidden',
    borderTopWidth: 1,
    paddingTop: 5,
    paddingBottom: 10
  },
  tab: {
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1
  }
})

const MainMenuIcons = ['user-secret', 'home']

class TabBar extends Component {
  render() {
    const { navigation, jumpTo, activeTintColor, inactiveTintColor } = this.props
    const { routes } = navigation.state
    const { tabbar, tab } = styles

    return (
      <View style={tabbar}>
        {routes.map((route, index) => {
          const { key } = route
          const focused = index === navigation.state.index
          const textColor = focused ? activeTintColor : inactiveTintColor
          return (
            <TouchableWithoutFeedback key={`${key}`} onPress={() => jumpTo(key)}>
              <View style={tab}>
                <ButtonTab icon={MainMenuIcons[index]} tintColor={textColor} />
              </View>
            </TouchableWithoutFeedback>
          )
        })}
      </View>
    )
  }
}
export { TabBar }
```

##ButtonTab

Create a component ButtonTab 
src/components/TabBar/ButtonTab.js

```javascript
// @flow
import React, { memo } from 'react'
import Fontisto from 'react-native-vector-icons/Fontisto'

type Props = {
  icon: 'user-secret' | 'home',
  tintColor: string,
  mainTab: boolean
}

const ButtonTab = memo<Props>(({ icon, tintColor }) => <Fontisto name={icon} size={35} color={tintColor} />)

export { ButtonTab }
```

Add export to src/components/index.js

```diff
export * from './Localei18n'
export * from './AppContainer'
export * from './Header'
export * from './Loading'
export * from './Space'
export * from './Button'
export * from './TextLink'
export * from './TextError'
export * from './Input'
+export * from './TabBar'
+export * from './CardJob'
+export * from './CardJobDetail'
+export * from './Card'
```

##AppNavigator

Editing the navigation configuration file for our custom authentication src/AppNavigator.js

```javascript
import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import { createBottomTabNavigator } from 'react-navigation-tabs'
import { ConfirmSignUp, Hello, SignIn, SignUp, Forgot, ForgotPassSubmit } from './screens/Authenticator'
import { User, Jobs } from './screens'
import { TabBar } from './components'
import { PURPLE } from './constants'

const TabNavigator = {
  screen: createBottomTabNavigator(
    {
      JOBS: { screen: Jobs },
      USER: { screen: User }
    },
    {
      initialRouteName: 'JOBS',
      tabBarComponent: TabBar,
      tabBarPosition: 'bottom',
      animationEnabled: true,
      lazy: true,
      backBehavior: 'initialRoute',
      tabBarOptions: {
        showLabel: false,
        activeTintColor: PURPLE,
        inactiveTintColor: '#390032'
      }
    }
  )
}

const AppNavigator = createStackNavigator(
  {
    HELLO: { screen: Hello },
    SIGN_IN: { screen: SignIn },
    SIGN_UP: { screen: SignUp },
    FORGOT: { screen: Forgot },
    CONFIRM_SIGN_UP: { screen: ConfirmSignUp },
    FORGOT_PASSWORD_SUBMIT: { screen: ForgotPassSubmit },
    MAIN: TabNavigator
  },
  {
    initialRouteName: 'HELLO',
    headerMode: 'none',
    defaultNavigationOptions: {
      gesturesEnabled: false
    }
  }
)

export default createAppContainer(AppNavigator)
```

Build the application and test

Done✅


#REFERENCES:

1. [AWS Amplify](https://aws-amplify.github.io)

2. [Fullstack Serverless](https://learning.oreilly.com/library/view/full-stack-serverless/9781492059882)

3. [Core Features, Architecture, Pros and Cons](https://www.altexsoft.com/blog/engineering/graphql-core-features-architecture-pros-and-cons)

4. [graphql.org](https://graphql.org/learn)