Fullstacking: Creating the app
Published: 2019-09-05 18:57:36

We're finally creating the app itself. The challenge require us to create a list which different users can access and add objects. At first, I thought about creating a selling used items list, however we shall create an event list instead.
Our event will be composed of a Title, a Date, a Description and an Author.

## Event components

Let's start by creating `EventCard` and `EventList` components and updating the query to get more information from events. Note whenever you change a **graphql query**, you must run `yarn relay`
```react
// packges/app/src/components/EventList.js
import React from 'react';
import {ScrollView, Text} from 'react-native';
import {graphql} from 'babel-plugin-relay/macro';
import {QueryRenderer} from 'react-relay';
import Environment from '../relay/Environment';
import EventCard from './EventCard';

const EventList = ({query}) => {
  const {events} = query;
  return (
    <>
      <Text>Event List</Text>
      <ScrollView>
        {events.map(event => (
          <EventCard key={event.id} event={event} />
        ))}
      </ScrollView>
    </>
  );
};

const EventListQR = () => {
  return (
    <QueryRenderer
      environment={Environment}
      query={graphql`
        query EventListQuery {
          events {
            id
            title
            date
            description
          }
        }
      `}
      variables={{}}
      render={({error, props}) => {
        console.log('qr: ', error, props);
        if (error) {
          return <Text>{error.toString()}</Text>;
        }

        if (props) {
          return <EventList query={props} />;
        }

        return <Text>loading</Text>;
      }}
    />
  );
};

export default EventListQR;
```
```react
/// packages/app/src/components/EventCard.js
import React from 'react';
import {Text} from 'react-native';

const EventCard = ({event}) => {
  return (
    <>
      <Text>Title: {event.title}</Text>
      <Text>Date: {event.date}</Text>
      <Text>Description: {event.description}</Text>
      <Text>Author: {event.author}</Text>
    </>
  );
};

export default EventCard;
```
You'll also need an updated `schema.graphql`. Create an **Event Type** and **Event Model** in the server's package as you did with Product. Run `yarn update-schema` and copy the server's `graphql.schema` to the app's `graphql.schema`. Check this [commit](https://github.com/Markkop/fullstacking/commit/ca91c9cc42039300525dee692e165012f6bccf48) to some details.
Run `yarn relay` to compile the graphql query's code.

Then we can import the EventList component into our App component and render it
```react
// packages/app/src/App.js
const App = ({query}) => {
  const {products} = query;

  return (
    <Fragment>
      <EventList />
    </Fragment>
  );
};
```

## EventCreate with Formik

First we have to configure our Mutation root type in our server

```javascript
// packages/server/graphql/schema.js
const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLID,
  GraphQLList,
  GraphQLNonNull,
  GraphQLString
} = require("graphql");
const { fromGlobalId, mutationWithClientMutationId } = require("graphql-relay");
const eventGraphQLType = require("./eventType");
const Event = require("../models/Event");

const Query = new GraphQLObjectType({
  name: "Query",
  fields: {
    event: {
      type: eventGraphQLType,
      args: { id: { type: GraphQLNonNull(GraphQLID) } },
      resolve(parent, args) {
        return Event.findById(fromGlobalId(args.id).id);
      }
    },
    events: {
      type: GraphQLList(eventGraphQLType),
      resolve() {
        return Event.find();
      }
    }
  }
});

const EventCreate = mutationWithClientMutationId({
  name: "EventCreate",
  inputFields: {
    title: {
      type: new GraphQLNonNull(GraphQLString)
    },
    date: {
      type: new GraphQLNonNull(GraphQLString)
    },
    description: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  outputFields: {
    id: {
      type: GraphQLID,
      resolve: payload => payload.id
    }
  },
  mutateAndGetPayload: async ({ title, date, description }) => {
    const newEvent = new Event({
      title,
      date,
      description
    });
    const returnedObject = await newEvent.save();
    const eventId = await returnedObject._id;
    console.log(`New Event created with id: ${eventId}`); //this will be in a subscription

    return {
      id: eventId
    };
  }
});

const Mutation = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    EventCreate: EventCreate
  }
});

module.exports = new GraphQLSchema({
  query: Query,
  mutation: Mutation
});

```
*(we might want to split this code soon)*

We're creating the root **Mutation type** and a **EventCreate** mutation type. This new type receives a **title**, **date** and **description** string and returns the new event's **id**.

Run `yarn update-schema` to create `schema.graphql` in the server's package. It should be looking like this.

```javascript
type Event {
  id: ID!
  _id: String
  title: String
  date: String
  description: String
  author: String
}

input EventCreateInput {
  title: String!
  date: String!
  description: String!
  clientMutationId: String
}

type EventCreatePayload {
  id: ID
  clientMutationId: String
}

type Mutation {
  EventCreate(input: EventCreateInput!): EventCreatePayload
}

type Query {
  event(id: ID!): Event
  events: [Event]
}
```

Now copy `schema.graphql` to replace the same file in the app's package.

Then create an `EventCreate` component with **Formik** as it follows:
```react
// packages/app/components/EventCreate.js
import React from 'react';
import {TextInput, Button, ButtonText} from 'react-native';
import {Formik} from 'formik';
import EventCreateMutation from './EventCreateMutation';

const EventCreate = () => {
  const handleSubmit = values => {
    const {title, date, description} = values;

    const input = {
      title,
      date,
      description,
    };

    const onCompleted = id => {
      // Some implementation that requires the id from
      // the new event created
      alert(JSON.stringify(id));

      // Redirect
      // this.props.navigation.navigate('UserList');
    };

    const onError = err => {
      console.error(err);
    };

    EventCreateMutation.commit(input, onCompleted, onError);
  };
  return (
    <Formik
      initialValues={{title: '', date: '', description: ''}}
      onSubmit={values => handleSubmit(values)}>
      {({values, handleChange, handleSubmit}) => (
        <>
          <TextInput
            placeholder="Title"
            onChangeText={handleChange('title')}
            value={values.title}
          />
          <TextInput
            placeholder="Date"
            onChangeText={handleChange('date')}
            value={values.date}
          />
          <TextInput
            placeholder="Short description"
            onChangeText={handleChange('description')}
            value={values.description}
          />
          <Button onPress={handleSubmit} title="Add Event"></Button>
        </>
      )}
    </Formik>
  );
};

export default EventCreate;

```
To use mutations, you have to create a **mutation file** and commit it as you can see above.

```javascript
// packages/app/components/EventCreateMutation.js
import {commitMutation, graphql} from 'react-relay';
import Environment from '../relay/Environment';

const mutation = graphql`
  mutation EventCreateMutation($input: EventCreateInput!) {
    EventCreate(input: $input) {
      id
    }
  }
`;

function commit(input, onCompleted, onError) {
  return commitMutation(Environment, {
    mutation,
    variables: {
      input,
    },
    onCompleted,
    onError,
  });
}

export default {commit};
```

We now can add new Events and after reloading (via `yarn shake` for example) we can see that our new event is in the list. We might not worry about live update and subscriptions yet.

## Navigation

In React we could use react-router, but in React-Native we're going to use `react-navigation` install it and its dependencies by running the following inside `packages/app`:
`yarn add react-native-reanimated react-native-gesture-handler react-native-screens`
And add the navigator we're going to use:
`yarn add react-navigation-tabs`

Now edit `App.js` as it follows:
```react
import React from 'react';
import {createAppContainer} from 'react-navigation';
import { createMaterialTopTabNavigator } from 'react-navigation-tabs';
import EventList from './EventList';
import EventCreate from './EventCreate';

const App = createMaterialTopTabNavigator(
  {
    EventCreate: {screen: EventCreate},
    EventList: {screen: EventList},
  },
  {
    initialRouteName: 'EventList',
  },
);

export default createAppContainer(App);

```
And then we can navigate between our screens. 
Later we migth change this navigator to another one.


## Debugging tips
Run `yarn relay` always after adding/changing a graphql query
Run `yarn update-schema` always after changing `schema.js`
Keep both `schema.graphql` files identical
Run `yarn android` after changing app's dependencies
Try `yarn start:app --reset-cache`
Run `yarn redirect`
Close and open app in the smartphone

## References

[React Native + Formik + Yup ❤️](https://medium.com/fotontech/react-native-formik-yup-%EF%B8%8F-18465e020ea0)
[Formik documentation](https://jaredpalmer.com/formik/docs/guides/react-native)
[React Navigation documentation](https://reactnavigation.org/docs/en/hello-react-navigation.html)
[GraphQL.js documentation](https://github.com/graphql/graphql-relay-js)