DEV Community

Cover image for Fullstacking: Creating the app
Mark Kop
Mark Kop

Posted on

Fullstacking: Creating the app

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

// 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;
Enter fullscreen mode Exit fullscreen mode
/// 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;
Enter fullscreen mode Exit fullscreen mode

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 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

// packages/app/src/App.js
const App = ({query}) => {
  const {products} = query;

  return (
    <Fragment>
      <EventList />
    </Fragment>
  );
};
Enter fullscreen mode Exit fullscreen mode

EventCreate with Formik

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

// 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
});

Enter fullscreen mode Exit fullscreen mode

(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.

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]
}
Enter fullscreen mode Exit fullscreen mode

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

Then create an EventCreate component with Formik as it follows:

// 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;

Enter fullscreen mode Exit fullscreen mode

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

// 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};
Enter fullscreen mode Exit fullscreen mode

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:

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);

Enter fullscreen mode Exit fullscreen mode

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 ❤️
Formik documentation
React Navigation documentation
GraphQL.js documentation

Top comments (0)