How to create a React Native app with PostgreSQL and GraphQL: Part 2

bnevilleoneill

Brian Neville-O'Neill

Posted on January 13, 2020

How to create a React Native app with PostgreSQL and GraphQL: Part 2

Written by Austin Roy Omondi✏️

In the previous article, we created a GraphQL server with NodeJS and Express. We will now look to consume the endpoints in the server we created using a React Native mobile app. React Native allows us to develop applications that run on both iOS and Android devices. We’ll be using Expo to bootstrap our app, this will also allow us to be able to quickly build and deploy our application.

With Expo, we can also run our app on mobile devices or on the web browser. Before we get started, Expo needs to be installed globally, to do so, run:

npm install expo-cli --global
Enter fullscreen mode Exit fullscreen mode

Now you are ready to get started.

Set up the application

To create the application, run this command:

expo init apollo-react-native-app
Enter fullscreen mode Exit fullscreen mode

This will generate a sample app that you can access via the Expo app on your own smartphone without installing it. To do this you will need to download the app from the Apple appstore for iOS or the Google Play store for Android.

Start the application by running expo start in the terminal from the root directory of your project and it will show you a QR code that you can scan in the Expo app and view an instance of the React Native application. Pretty neat, right?

Now that we have the barebones set up for our app, let us work on hooking it up to GraphQL by configuring our Apollo Client.

LogRocket Free Trial Banner

Configure the Apollo Client and the base app

The Apollo Client for React is a complete state management library that will take care of requesting, caching, and managing data provided by a GraphQL server like the one we built in Part 1. We shall set up the client at our application’s entry point, App.js . But first, we need to install the necessary dependencies by running the command below:

npm install apollo-client apollo-cache-inmemory apollo-link-http react-apollo --save
Enter fullscreen mode Exit fullscreen mode

What do these packages do?

  • apollo-client— as mentioned above, this takes care of any data exchange with the server as well as state management and caching
  • apollo-cache-inmemory— as of Apollo Client 2.0 the caching functionality was abstracted to apollo-cache-inmemory to make use of the caching capabilities without the need to rely on Redux
  • apollo-link-http— this handles network requests such as fetching and sending data
  • react-apollo— will supply an ApolloProvider that will wrap around the app and take care of state management, similar to React’s Context API

Now that we understand what each piece does in our app, let’s get to the setup. In App.js import these dependencies and set up your client like this:

import * as React from "react";
import { Platform, StatusBar, StyleSheet, View } from "react-native";

import { AppLoading } from "expo";

import { Asset } from "expo-asset";
import * as Font from "expo-font";

import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { ApolloProvider } from "react-apollo";

import AppNavigator from "./navigation/AppNavigator";

//Define your client for your ApolloProvider connecting to your graphql server.
const client = new ApolloClient({
  // initialize cache
  cache: new InMemoryCache(),
  //Assign your link with a new instance of a HttpLink linking to your graphql server
  link: new HttpLink({
    uri: "https://graphql-server-node-js-103.herokuapp.com/graphql"
  })
});

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

export default class App extends React.Component {
  state = {
    isLoadingComplete: false
  };

  loadResourcesAsync = async () => {
    await Promise.all([
      Asset.loadAsync([
        // load assets here
      ]),
      Font.loadAsync({
        // load fonts here
      })
    ]);
  };

  handleLoadingError = () => {
    // Any error handling can be done here
  };

  handleFinishLoading = () => {
    this.setState({ isLoadingComplete: true });
  };

  render() {
    const { isLoadingComplete } = this.state;
    const { skipLoadingScreen } = this.props;
    if (!isLoadingComplete && !skipLoadingScreen) {
      return (
        <AppLoading
          startAsync={this.loadResourcesAsync}
          onError={this.handleLoadingError}
          onFinish={this.handleFinishLoading}
        />
      );
    }
    return (
      <ApolloProvider client={client}>
        <View style={styles.container}>
          <AppNavigator />
        </View>
      </ApolloProvider>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In the client configuration above, our ApolloClient takes two arguments, cache which is an instance of inMemoryCache from apollo-cache-inmemory that handles data caching to speed up the app and link from apollo-link-http which is a terminating link that fetches GraphQL results from a GraphQL endpoint over an HTTP connection. For the purpose of this article, we have this linked to a deployed instance of our backend server but feel free to replace it with your own locally running server for your application if you wish.

The client configuration is then passed to the app through the ApolloProvider higher-order component as a client prop, allowing the application to gain all the functionality we set up above.

An additional component, AppLoading , has also been imported from Expo, it is a React component that tells Expo to keep the app loading screen open if it is the first and only component rendered in your app. This component can allow the application to load any assets needed by the application prior to starting it, this creates a better user experience.

It takes three props in our case:

  • startAsync (func) – A function that returns a Promise, and the Promise should resolve when the app is done loading the required data and assets
  • onError (func) – If startAsync throws an error, it is caught and passed into the function provided to onError
  • onFinish (func) – This is called when startAsync resolves or rejects. This should be used to set state and unmount the AppLoading component

You may have noticed some additional setup in the code above, not to worry, we’ll go through exactly what each part of that does shortly.

Create our navigator

We have an AppNavigator component in the setup above, this takes care of routing in our App with react-navigation. This is what we’ll be setting up next.

To do this, we need to install some dependencies first:

npm install react-navigation react-navigation-transistions
Enter fullscreen mode Exit fullscreen mode

These two packages work together to allow our app to navigate between screens with smooth transitions, react-navigation handles routing and navigation while react-navigation-transitions allows us to create custom transitions between our screens.

Create a navigation folder at the root of your project and in it create three files AppNavigator.js, MainTabNavigator.js , and styles.js.

Just like before, we first declare our styles:

//styled.js
import styled from "styled-components/native";

const MenuStyle = {
  backgroundColor: "#fcfaf7",
  borderBottomWidth: 0,
  elevation: 0
};

const IconStyle = styled.Image`
  height: 24;
  width: 24;
`;

export { MenuStyle, IconStyle };
Enter fullscreen mode Exit fullscreen mode

Next, we use createStackNavigator from react-navigation to provide a way for your app to transition between screens where each new screen is placed on top of a stack.

It takes two parameters, the first being an object containing our route configurations(routeConfig) and the second being the navigator configuration(StackNavigatorConfig):

//MainTabNavigator.js
import React from "react";
import { createStackNavigator } from "react-navigation";
import { fromBottom } from "react-navigation-transitions";
import { TouchableOpacity, View } from "react-native";

import HomeScreen from "../screens/HomeScreen";
import AddNoteScreen from "../screens/AddNoteScreen";
import { Images } from "../constants";
import { MenuStyle, IconStyle } from "./styled";

//Assign your goback navigator to variable call goBackHeader
const goBackHeader = goBack => (
  <TouchableOpacity
    activeOpacity={1}
    style={{ paddingHorizontal: 20 }}
    onPress={() => goBack()}
  >
    <View>
      <IconStyle source={Images.back} style={{ height: 15, width: 9 }} />
    </View>
  </TouchableOpacity>
);

const HomeStack = createStackNavigator(
  {
    Home: {
      screen: HomeScreen,
      navigationOptions: ({ navigation: { goBack } }) => ({
        headerStyle: MenuStyle
      })
    },
    NewNote: {
      screen: AddNoteScreen,
      navigationOptions: ({ navigation: { goBack } }) => ({
        headerStyle: MenuStyle,
        headerLeft: () => goBackHeader(goBack)
      })
    }
  },
  {
    transitionConfig: ({ scenes }) => {
      const nextScene = scenes[scenes.length - 1];
      if (nextScene.route.routeName === "NewNote") return fromBottom(550);
    }
  }
);

export default HomeStack;
Enter fullscreen mode Exit fullscreen mode

For our routeConfig, we have two screens Home and NewNote, which we will create soon. Each screen is declared as an object which will have screen and navigationOptions within it. In this case, screen maps onto the React component to be rendered for that screen and navigationOptions which can be used to pass a number of options to each component.

We have passed two options below and they are:

  • headerLeft – A function that returns a React element to display on the left side of the header. When a function is used, it receives a number of arguments when rendered (onPress, title, titleStyle , and more)
  • headerStyle – Style object for the header

On the NewNote screen we pass the headerStyle as well as headerLeft where we map a back button component that we declared using the goBackHeader function. This component takes a goBack prop which we destructured from react-navigation’s navigate prop. goBack is a function that closes the active screen and moves back in the stack, similar to the back button on the browser.

Our StackNavigatorConfig is simply an object containing the transitionConfig which we use to create a custom transition that causes our new screens to come into view from the bottom up rather than horizontally (which is the default).

We then pass our HomeStack to createSwitchNavigator and pass that to createAppContainer to create an app container that will help with linking your top-level navigator to the app environment. The purpose of SwitchNavigator is to only ever show one screen at a time.

//AppNavigator.js
import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';

import MainTabNavigator from './MainTabNavigator';

export default createAppContainer(
  createSwitchNavigator({
    Main: MainTabNavigator,
  })
);
Enter fullscreen mode Exit fullscreen mode

You can read more about both createSwitchNavigator and createStackNavigator in the react-navigation documentation.

Create the components

Our app will have a couple of reusable components that will make up its various screens, which is how we refer to pages when working with React Native. Consequently, we’ll look to build these components before we get to building the various screens of our app.

In our case, we have four main components we’ll be reusing across our application, Button, Card, TextInput, and KeyboardWrapper. Create a components folder in the root directory of the project and inside it create a folder for each of these components.

We also have some assets and constants that we’ll be using across the entire application. The assets are mainly fonts and images. Create an assets folder at root directory of the project by running mkdir assets and copy the contents of this folder into it. You can now import them for use in the application.

Create a constants folder as well, this will hold any constant variables that we may make use of in the application and help with easy reference.

Create a Colors.js file in the constants folder, this file will hold the colors that we make use of across the app and will look like this:

const white = "#ffffff";
const darkGray = "#595A5A";
const orange = "#FFA500";
const mediumGray = "#9B9B9B";
const strokeGray = "#E6E6E6";

export { orange, darkGray, white, mediumGray, strokeGray };
Enter fullscreen mode Exit fullscreen mode

In the constants folder, create an Images.js and add the following code:

const Images = {
  more: require("../assets/images/more.png"),
  back: require("../assets/images/back.png"),
  menu: require("../assets/images/menu.png")
};
export default Images;
Enter fullscreen mode Exit fullscreen mode

This makes it easier to import the images for use.

We’ll also have a Layout.js file where we will set the base layout of the application.

import { Dimensions } from 'react-native';

const width = Dimensions.get('window').width;
const height = Dimensions.get('window').height;

export default {
  window: {
    width,
    height,
  },
  isSmallDevice: width < 375,
};
Enter fullscreen mode Exit fullscreen mode

You can make importing from the constants directory even easier by exporting the declared constants from an index.js file within the directory. Here’s how you can do it with the images:

import Images from "./Images";
export { Images };
Enter fullscreen mode Exit fullscreen mode

Now that we have our major resources set up, let us get to creating the individual components that we will reuse.

Button

Let’s create our first component, a Button component, create two files, index.js and styled.js in the Button directory. In styled.js place the following code, which will handle the base styling of the Button component with styled-components:

import styled from "styled-components/native";
const white = "#FFFFFF";

const baseButtonStyles = `
  letter-spacing: 0.5px;
  font-size: 12px;
  color: ${white}
`;

const ButtonStyle = `
  ${baseButtonStyles}
  background-color:#4A4A4A;
    color: ${white}
`;

const StyledButton = styled.TouchableOpacity`
  ${ButtonStyle}
  align-items: center;
`;

const StyledButtonText = styled.Text`
  color: ${white};
  line-height: 19px;
`;

const ButtonText = styled.Text`
  font-family: WorkSans-SemiBold;
  color: ${white};
  line-height: 19px;
  justify-content: center;
  align-items: center;
`;

export { ButtonText, StyledButtonText, StyledButton };
Enter fullscreen mode Exit fullscreen mode

Now in index.js add this:

import React from "react";
import { ifIphoneX } from "react-native-iphone-x-helper";

import { StyledButtonText, StyledButton } from "./styled";

const Button = props => {
  const { title, ...rest } = props;
  return (
    <StyledButton
      activeOpacity={0.7}
      color={"#4A4A4A"}
      underlayColor={"#4A4A4A"}
      {...rest}
    >
      <StyledButtonText
        style={{
          ...ifIphoneX(
            {
              paddingTop: 27,
              paddingBottom: 50
            },
            {
              paddingTop: 27,
              paddingBottom: 26
            }
          )
        }}
      >
        {title.toUpperCase()}
      </StyledButtonText>
    </StyledButton>
  );
};

export default Button;
Enter fullscreen mode Exit fullscreen mode

We import the styled-components we declared in styled.js and use them to create our button. You will also notice we have imported ifIphoneX from react-native-iphone-x-helper which helps us account for the difference in layouts of the iPhone X, XS, XS Max & XR. This method allows us to create stylesheets with the iPhone X in mind. Taking in two parameters, the first is the styling on iPhone X and the second is the regular styling and applies them accordingly.

Card

Next, we will create our Card component which will be our main display component, we will use the card to display each note. Just as we did with the button, we shall first create our styled-components inside a styled.js:

import styled from "styled-components/native";
import {
  strokeGray,
  darkGray,
  white,
  mediumGray
} from "../../constants/Colors";

const CardContainer = styled.View`
  background-color: #d8d8d8;
  border-style: solid;
  width: 100%;
  padding: 16px 16px 16px 16px;
  text-align: center;
  border-radius: 9px;
  margin-bottom: 20px;
`;

const TimestampContainer = styled.Text`
  font-size: 10px;
  text-transform: uppercase;
  line-height: 12px;
  color: ${mediumGray};
  padding-top: 16px;
`;

const EditWrapper = styled.TouchableOpacity`
  margin-left: auto;
  margin-top: 5px;
`;

const EditIcon = styled.Image`
  height: 4px;
  width: 20px;
`;

const CardText = styled.Text`
  font-weight: 500;
  color: ${darkGray};
  line-height: 23px;
  font-size: 16px;
  width: 90%;
`;

const HeaderContainer = styled.View`
  display: flex;
  flex-direction: row;
`;

export {
  CardContainer,
  TimestampContainer,
  EditWrapper,
  EditIcon,
  CardText,
  HeaderContainer
};
Enter fullscreen mode Exit fullscreen mode

We then import them into index.js just as we did for the button:

import React from "react";
import PropTypes from "prop-types";
import { Images } from "../../constants";

import {
  CardContainer,
  TimestampContainer,
  EditWrapper,
  EditIcon,
  CardText,
  HeaderContainer
} from "./styled";

const NoteCard = props => {
  const { onOptions, noteText } = props;
  return (
    <CardContainer>
      <HeaderContainer>
        <CardText>{noteText}</CardText>
        <EditWrapper onPress={onOptions}>
          <EditIcon source={Images.more} />
        </EditWrapper>
      </HeaderContainer>
      <TimestampContainer>1 hour ago</TimestampContainer>
    </CardContainer>
  );
};
export default NoteCard;
Enter fullscreen mode Exit fullscreen mode

KeyboardWrapper

This component triggers the virtual keyboard on your mobile device, we use React Native’s KeyboardAvoidingView component which allows the view to move out of the way of the virtual keyboard by adjusting it’s size and position accordingly:

import React from "react";
import { KeyboardAvoidingView, Platform } from "react-native";
import { Header } from "react-navigation";
import { ifIphoneX } from "react-native-iphone-x-helper";
import PropTypes from "prop-types";

const KeyboardWrapper = props => {
  const { children } = props;

  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === "android" ? null : "padding"}
      keyboardVerticalOffset={
        ifIphoneX ? Header.HEIGHT + 1 : Header.HEIGHT + 18
      }
      style={{ flex: 1 }}
    >
      {children}
    </KeyboardAvoidingView>
  );
};

KeyboardWrapper.propTypes = {
  children: PropTypes.node.isRequired
};

export default KeyboardWrapper;
Enter fullscreen mode Exit fullscreen mode

TextInput

This component will do just what its name suggests, act as an input for our text, complete with a placeholder. First, let’s declare our styles in styled.js:

import styled from "styled-components/native";

const BaseStyles = `

  border-width: 1px;
  border-style: solid;
  width: 100%;
  padding: 10px 16px;
`;
const InputStyles = `
  ${BaseStyles}
  font-size: 16px;
`;

const StyledTextArea = styled.TextInput`
  ${InputStyles}
  flex: 1;
  background-color: transparent;
  border-radius: 0;
  padding-left: 0px;
  border-width: 0;
  font-size: 20px;
  margin-bottom: 0;
  color: #ffffff;
  /* font-family: WorkSans-Regular; */
`;

export { StyledTextArea };
Enter fullscreen mode Exit fullscreen mode

Now, let us create our placeholder, we wrap a Text element created using styled-components in a TouchableOpacity element created in a similar way. This allows the app to respond to you touching the placeholder by pulling up your keyboard for you to type in your notes:

import React from "react";
import styled from "styled-components/native";

const chGray = "#E6E4E3";

const RowContainer = styled.TouchableOpacity`
  flex-direction: row;
  width: 100%;
  background-color: ${chGray};
  border-radius: 30px;
  width: 100%;
  margin-bottom: 24px;
  padding: 8px 16px;
`;

const StyledTextInput = styled.Text`
  /* font-family: WorkSans-Regular; */
  color: #4a4a4a;
  opacity: 0.8;
  font-size: 14px;
  line-height: 22px;
`;

const TextPlaceHolder = props => {
  const { text, onHolderPress } = props;
  return (
    <RowContainer activeOpacity={1} onPress={onHolderPress}>
      <StyledTextInput>{text}</StyledTextInput>
    </RowContainer>
  );
};

export default TextPlaceHolder;
Enter fullscreen mode Exit fullscreen mode

Now let us create our TextArea component which is where the text we type in will appear, we already created it in styled.js as StyledTextArea here we just pass some constants (specifically the placeholder text color) to it along with some props:

import React from "react";
import { mediumGray } from "../../constants/Colors";

import { StyledTextArea } from "./styled";

const TextArea = props => {
  const { ...rest } = props;
  return (
    <StyledTextArea
      keyboardAppearance="dark"
      placeholderTextColor={"#4a4a4a"}
      {...rest}
    />
  );
};

export default TextArea;
Enter fullscreen mode Exit fullscreen mode

Now let us export everything in index.js for cleaner imports in other files:

import TextPlaceHolder from "./Placeholder";
import TextArea from "./TextArea";

export { TextPlaceHolder, TextArea };
Enter fullscreen mode Exit fullscreen mode

Create screens and add the GraphQL integration

Screens are the pages of our application, each made up of a number of components working together. Our application has two main screens. Just like our components, we will first use styled-components to define the styles of our screen elements inside a styled.js file:

import styled from "styled-components/native";

const Container = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #fcfaf7;
`;

const NotesContainer = styled.View`
  flex: 1;
  background-color: #fcfaf7;
`;

const NotesWrapper = styled.View`
  padding: 20px;
`;

const PlaceholdeWrapper = styled.View`
  padding-top: 12px;
`;

const HeaderText = styled.Text`
  font-weight: 900;
  font-size: 36px;
  padding-bottom: 20px;
  color: #b89b72;
`;

export {
  Container,
  PlaceholdeWrapper,
  NotesContainer,
  NotesWrapper,
  HeaderText
};
Enter fullscreen mode Exit fullscreen mode

HomeScreen

The HomeScreen will be the central screen where all the notes we have created will be displayed. This is where we will view and delete notes and when empty(no notes added) it will look like this for iPhone users:

empty notes for iphone users

Once notes are added it will then look like this:

ui with notes added

Inside the screens directory, create a HomeScreen.js file which we will be working with for this.

First, let us import some components and dependencies for the screen as well as define our GraphQL query and mutation variables as GET_NOTES and DELETE_NOTE respectively. We do this to tell our API what pieces of data we would like returned for use:

import React, { Component } from "react";
import {
  ScrollView,
  FlatList,
  ActivityIndicator,
  View,
  Alert
} from "react-native";
import ActionSheet from "react-native-actionsheet";
//Import the Query component from react apollo that will responsible for retrieving data from your graphql server.
import { Query, Mutation } from "react-apollo";
//import gql from graphql-tag for making queries to our graphql server.
import gql from "graphql-tag";

import { Container, PlaceholdeWrapper, HeaderText } from "./styled";
import { Button, NoteCard, TextPlaceHolder } from "../components";

//Define your query variable which is the query responsible for retrieving data
//This will query all notes
const GET_NOTES = gql`
  query {
    notes {
      id
      text
    }
  }
`;

const DELETE_NOTE = gql`
  mutation DeleteNote($id: ID!) {
    deleteNote(id: $id) {
      id
      text
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Next, let us define our HomeScreen class component and add some utility functions which will be responsible for the following purposes:

  • _addNewNote — navigates the user to the New Note screen
  • _renderItem — renders a notecard
  • _showEditDeleteOptions — shops an edit/delete option for a given note as shown here:

showDeleteOptions

  • _deletePostPrompt — prompts the user to delete or cancel deletion of a note
  • _deleteNote — prompts a GraphQL delete mutation on a given note
class HomeScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      noteId: null
    };
  }

  _addNewNote = () => {
    const { navigation } = this.props;
    navigation.navigate("NewNote", {});
  };

  _renderItem({ item }) {
    //Return the UI
    // It will return a list of all notes
    return (
      <NoteCard
        noteText={item.text}
        onOptions={() => this._showEditDeleteOptions(item.id)}
      />
    );
  }

  _showEditDeleteOptions = async noteId => {
    await this.setState({ noteId });
    this.deleteActionSheet.show();
  };

  _deletePostPrompt = noteId => {
    Alert.alert("Delete Note ?", null, [
      {
        text: "Delete",
        onPress: () => {
          this._deleteNote({ noteId });
        }
      },
      {
        text: "Cancel",
        style: "cancel"
      }
    ]);
  };

  _deleteNote = noteId => {
    <Mutation
      mutation={DELETE_NOTE}
      update={store => {
        const storeNotes = store.readQuery({ query: GET_NOTES });
        const data = storeNotes.notes.filter(note => note.id !== noteId);
        store.writeQuery({
          query: GET_NOTES,
          data: { notes: [...data] }
        });
      }}
    >
    </Mutation>;
  };

 //render here

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

Notes are fetched and displayed using react-apollo’s Query component which takes in a query prop that is the description of the data we would like to fetch from our GraphQL API. It then conditionally renders UI as it’s child component depending on the data fetched as well as the error and loading states. We will include it inside our render function, we will replace the //render here comment with this:

render() {
  const { noteId } = this.state;
  return (
    <Container>
      <View>
        <ActionSheet
          ref={o => (this.deleteActionSheet = o)}
          options={["Delete", "Cancel"]}
          cancelButtonIndex={1}
          destructiveButtonIndex={0}
          onPress={index => {
            if (index === 0) this._deletePostPrompt(noteId);
          }}
        />
      </View>
      <ScrollView showsVerticalScrollIndicator={false}>
        <HeaderText>My Notes</HeaderText>
        <PlaceholdeWrapper>
          <TextPlaceHolder
            onHolderPress={() => this._addNewNote()}
            text={"Add new note"}
          />
        </PlaceholdeWrapper>
        <Query query={GET_NOTES}>
          {/* The props.children of the Query will be a callback with a response, and error parameter. */}
          {(response, error, loading) => {
            if (error) {
              return <Text style={styles.errorText}>{error}</Text>;
            }
            if (loading) {
              return <ActivityIndicator />;
            }
            //If the response is done, then will return the FlatList
            if (response) {
              //Return the FlatList if there is not an error.
              return (
                <FlatList
                  data={response.data.notes}
                  renderItem={item => this._renderItem(item)}
                />
              );
            }
          }}
        </Query>
      </ScrollView>
    </Container>
  );
}
}
Enter fullscreen mode Exit fullscreen mode

In the case above, any data fetched from our endpoint containing notes are passed to the FlatList component that then renders a list of cards through the renderItem prop. The renderItem prop is typically a function that returns a React component to be rendered from a piece of data.

Notes are deleted using react-apollo’s Mutation component which takes a mutation prop that is a GraphQL document describing the details of the note to be deleted. It also takes an update prop which is a function that updates the cache to reflect the changes following the deletion of a note.

AddNoteScreen

The AddNote is the screen that is responsible for taking in user input and using it to create a new note by passing the data to our GraphQL API. We will create it in a file:

import * as WebBrowser from "expo-web-browser";
import React, { Component } from "react";
import { ScrollView } from "react-native";
//import Mutation component for performing queries.
import { Mutation } from "react-apollo";
// import gql from graphql-tag to define your graphql schema
import gql from "graphql-tag";

import { NotesContainer, NotesWrapper } from "./styled";
import { Button, TextArea, KeyboardWrapper } from "../components";

const CREATE_NOTE = gql`
  mutation CreateNote($text: String!) {
    createNewNote(text: $text) {
      id
      text
    }
  }
`;

const GET_NOTES = gql`
  query {
    notes {
      id
      text
    }
  }
`;

class AddNoteScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      note: ""
    };
  }

  _addNote = postAction => {
    const { note } = this.state;
    const { navigation } = this.props;
    return (
      <Button
        onPress={() =>
          postAction({
            variables: { text: note }
          }).then(navigation.goBack())
        }
        title={"Add new note"}
      />
    );
  };

  render() {
    const { note } = this.state;

    return (
      <KeyboardWrapper>
        <NotesContainer>
          <ScrollView>
            <NotesWrapper>
              <TextArea
                autoFocus
                value={note}
                returnKeyType="next"
                placeholder={"Jot Something"}
                //onChangeText is basically a simplified version of onChange,
                //so you can easily use it, without the hassle of going through event.target.value to get changed value.
                onChangeText={text => this.setState({ note: text })}
              />
            </NotesWrapper>
          </ScrollView>
          <Mutation
            mutation={CREATE_NOTE}
            update={(store, { data: { createNewNote } }) => {
              const data = store.readQuery({ query: GET_NOTES });
              store.writeQuery({
                query: GET_NOTES,
                data: { notes: data.notes.concat([createNewNote]) }
              });
            }}
          >
            {(postAction, { loading, error }) => {
              return this._addNote(postAction);
            }}
          </Mutation>
        </NotesContainer>
      </KeyboardWrapper>
    );
  }
}

export default AddNoteScreen;
Enter fullscreen mode Exit fullscreen mode

Here’s what the screen would look like with the placeholder prior to the user typing any text:

add a new note

Our AddNoteScreen uses GraphQL’s Mutation component to create a new note, similar to how we deleted notes. In this case, the mutation prop is a GraphQL document containing the details of the new note to be created, it takes a text argument that is a string of text that will make up the contents of the note.

Mutation also takes an update prop similar to how it did when deleting a note that is responsible for updating the cache with the newly created note.

One major difference between the Query and Mutation components is their children. Query has React nodes as it’s children, rendering UI dependent on the result of running the query. Mutation, on the other hand, has a function as it’s children, this function triggers a mutation on our GraphQL API.

Here’s what your NewNote screen will look like once the text is typed in:

type new note

Conclusion

React Native and GraphQL work really well together to create a great mobile application. React Native allows developers to create “write once, run everywhere” applications because it’s compatible with both Android and iOS. GraphQL allows the application to only fetch specific pieces of information that they require, making them a lot more efficient. Overall, these two technologies are great options to consider when building mobile apps. You can play around with the information provided in this article and try to create an edit feature for the application we have built.


Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

Alt Text

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.


The post How to create a React Native app with PostgreSQL and GraphQL: Part 2 appeared first on LogRocket Blog.

💖 💪 🙅 🚩
bnevilleoneill
Brian Neville-O'Neill

Posted on January 13, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related