React Native

Query Renderer on Back4App

Introduction

In our previous guide, we have learned how to prepare our Relay Environment. Now you’re ready to start developing your React Native app.

In this guide, you will learn how to make your first query on Back4App. We’re going to dive into the Relay Query Render, understanding its main principles and use it to consume your first data from Back4App.

Goals

Get an overview of Relay Query Renderer;

Make a query on Back4App GraphQL API from a React Native App using Relay;

Prerequisites

  • Application created at Back4App dashboard
  • React Native application and with Relay Environment configured by the previous docs.
  • Read about Relay Node and Connections

What is the Query Renderer?

As well as React builds a tree of components, Relay builds a tree of data components. This means that each component of the application will be the owner of their fragment data. The fragment will contain the data information necessary to render it on screen and Relay ensures that this data is available before rendering the component.

Handling this whole approach, the Query Renderer is a root component necessary to compose those fragments and prepare the query to be fetched from our back-end.

Why understand the Query renderer?

Understanding the use of Query Renderer makes it important to abstract your application in different ways. A good abstract of code could prevent hours of work, errors, debugging time, etc.

How it works the renderer together with Back4app APIs

In the last tutorial, we have prepared the Relay Environment file, which specifies the Back4App connection info. Using this configuration, Relay will take care of the communication with Back4App APIs. You don’t need to worry about the connection. Just focus on building the data components.

Step 1 - Creating a Class on Back4App Dashboard

Let’s create your first class and populate it with a few objects using the Back4App GraphQL Console. The Person class has 2 fields name which is a string and salary which is an integer. Go to the Dashboard->Core->GraphQL Console and use the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mutation CreateClass {
  createClass(input:{
    name: "Person"
    schemaFields: {
      addStrings: [{name: "name"}]
      addNumbers: [{name: "salary"}]
    }
  }){
    class{
      schemaFields{
        name
        __typename
      }
    }
  }
}

You’ll see the result below:

  • creating class *

Now let’s create some objects inside this Class. Go to the create object mutation guide and see how to handle this case. Make sure you are using the latest Parse Server Version in order to use the most recent GraphQL API notation available on Back4App.

1
2
3
4
5
6
7
8
9
mutation CreateObject{
  createHero(input: {fields: {name: "Allana Foley", salary: 1000}}){
    person {
      id
      name
      salary
    }
  }
}

** creating object **

Now, the Person class is created and has a name and salary field.

After creating a new class, Back4App automatically generates all the necessary resources to use the back-end safely.

One example is the list of objects. Back4App already created the connections necessary to query the list of Person: People.

To better understand, go to the playground, refresh and open the docs tab and look for People. Back4App generated the connection field. You can also query the class person as a list. Note that the Query.people field is a PersonConnection.

Relay will consume the connection field to render a list of the Person’s objects.

Person Field doc:

And People (Person) connection Field docs:

Step 2 - Updating the Schema

It is important to remember that if a new class goes into your application, it will be necessary to update the schema inside the root of the React Native application.

If necessary, go to Download Schema docs and repeat the steps to update the schema.

Step 3 - First example of fragment container

Before we continue the tutorial, let’s introduce you to the fragment container.

Let’s create a component that will be the Person info owner. This component will contain the person’s name and salary. Here you can ask any person field to build your component. Now, we’re going to proceed with these two fields.

  • Creates a file and name it PersonCard.js
  • inside of it, let’s create a simple function component
1
2
3
4
5
6
7
8
9
import React from 'react';

const PersonCard = () => {
    return (
        <div>
            
        </div>
    );
};

Replace the line of export default by the code below:

1
2
3
4
5
6
7
8
9
export default createFragmentContainer(PersonCard, {
    person: graphql`
    fragment PersonCard_person on Person {
      id
      name
      salary
    }
  `,
});

The code above will create a fragment of a Person that asks only for id, name, and salary.

Finish updating the rest of the component with the following code:

1
2
3
4
5
6
7
8
const PersonCard = ({person}) => {
    return (
        <View>
            <Text>Name: {person.name}</Text>
            <Text>Salary: {person.salary}</Text>
        </View>
    );
};

The final result should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from "react";
import { createFragmentContainer, graphql } from "react-relay";
import { View, Text } from "react-native";

const PersonCard = ({person}) => {
    return (
        <View>
            <Text>Name: {person.name}</Text>
            <Text>Salary: {person.salary}</Text>
        </View>
    );
};

export default createFragmentContainer(PersonCard, {
    person: graphql`
    fragment PersonCard_person on Person {
      id
      name
      salary
    }
  `,
});

Step 4 - Creating The Query Renderer

The next step is to create the Query Renderer for your objects list. The Query Renderer is a root component for retrieving the data components and preparing them to fetch data from the backend. You will learn how to retrieve data for a Person Class on Back4App.

Step 4.1 - Creating the file

  • Create a new file and name it PersonRenderer.js
  • Copy the code below and paste it into PersonRenderer file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const PersonRenderer = () => {
  return (
    <QueryRenderer
      environment={Environment}
      query={graphql``}
      variables={null}
      render={({error, props}) => {
        if (error) {
          return (
            <View>
              <Text>{error.message}</Text>
            </View>
          );
        } else if (props) {
          // @todo here will be implement the person card for each item from result
        }
        return (
          <View>
            <Text>loading</Text>
          </View>
        );
      }}
    />
  );
};

export default PersonRenderer;

Step 4.2 - Understanding the props of QueryRenderer

Let’s start with a Query Renderer with their props empty: graphql, variables, and render. Step by step, you will implement each one incrementally.

First of all, your application needs to inform the query for Query Renderer. Here, our application will consume a list of People. On query props, paste the following code:

1
2
3
4
5
6
7
8
9
10
graphql`
  query PersonRendererQuery {
    people {
      edges {
        node {
          ...PersonCard_person
        }
      }
    }
  }`

The graphql comes from react-relay and implements the query as a string.

It is Important to understand edges and node connection. The query above is consuming a node connection of people from the Back4App server. Every time you create a new class, it will be followed by a node connection.

Variables

When necessary, the query renderer will consume variables. A good example: when the application requests a query for a person by id. As this is not the case right now, let’s pass by null on the variables props.

Populating the Person Card

This query will return a list of people. The query renderer ensures that the data will be available to render. If it doesn’t, shoot an error. The props responsible for this is the render.

Populate the render props with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render={({error, props}) => {
  if (error) {
    return (
      <View>
        <Text>{error.message}</Text>
      </View>
    );
  } else if (props) {
     return props.people.edges.map(({node}) => <PersonCard person={node} />);
  }
  return (
    <View>
      <Text>loading</Text>
    </View>
  );
}}

Replace the commented todo for a javascript map for rendering a person card for each result from the list.

As said, the query renderer takes responsibility for making the data available only when it is ready. Until then, a loading message will be displayed. If an error occurs, it will be displayed on the screen preventing an unexpected application crash.

By last, let improves the render of person replacing the .map by a new function. Put it before the Query Renderer:

1
2
3
const renderPersons = (people) => {
  return people.edges.map(({node}) => <PersonCard person={node} />);
};

And the final result should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React from "react";
import { QueryRenderer } from "react-relay";
import Environment from "./relay/Environment";
import PersonCard from "./PersonCard";
import { View, Text } from "react-native";

const PersonRenderer = () => {
  const renderPersons = (people) => {
    return people.edges.map(({node}) => <PersonCard person={node} />);
  };

  return (
    <QueryRenderer
      environment={Environment}
      query={graphql`
        query PersonRendererQuery {
          people {
            edges {
              node {
                ...PersonCard_person
              }
            }
          }
        }
      `}
      variables={null}
      render={({error, props}) => {
        if (error) {
          return (
            <View>
              <Text>{error.message}</Text>
            </View>
          );
        } else if (props) {
          return renderPersons(props.people);
        }
        return (
          <View>
            <Text>loading</Text>
          </View>
        );
      }}
    />
  );
};

export default PersonRenderer;

Step 5 - Making your first query

Now it is time to fetch the Person using the PersonRenderer. If everything is ok, your application now has two new components: PersonRenderer and PersonCard.

Before starting the application, the Relay needs the Relay Compiler to run and generate the component types. For this, run into your terminal:

1
yarn relay

On app.js add the followng code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react';
import { SafeAreaView, StatusBar, View, Text } from 'react-native';

import Providers from './Providers';
import PersonRenderer from '../components/home/person/PersonRenderer';

const App = () => {
  return (
    <Providers>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <View
          style={ {
            flexDirection: 'column',
            justifyContent: 'center',
            alignItems: 'center',
            marginTop: 100,
          } }>
          <Text style={ {fontWeight: "bold", textAlign: "center"} }>
            Back4App React Native Relay - Query Renderer List Example
          </Text>
          <PersonRenderer />
        </View>
      </SafeAreaView>
    </Providers>
  );
};

export default App;

The code of app.js comes originally from create-react-native-app. It added a View with a style to center the content on the screen with a margin of 10px from the top. Inside of it has a text with a label to give some context for the print and the PersonRenderer to show the list of person.

You need to get the following result:

Rendering

In our Back4App React Native application, we import the PersonRenderer directly into the App.js. As the PersonRenderer is a root component and has its QueryRenderer, the Person must be displayed without any error:

Step 6 - Typing the components

This step makes sense for an application that works with typescript. If your application doesn’t use typescript, go ahead.

One of the powers of the Relay Compiler is to generate the types from each data component. Let’s type the PersonRenderer and PersonCard to make the application more powerful.

Typing PersonRenderer

Type the renderPerson function arg people into PersonRenderer:

1
2
3
const renderPersons = (people: PersonRendererQuery['response']['people']) => {
  return people.edges.map(({node}) => <PersonCard person={node} />);
};

Import the PersonRendererQuery type from __generated__ folder created inside of the same folder of the PersonRenderer.

Typing PersonCard

Go ahead to PersonCard, create a new type object and name it PersonCardProps:

1
type PersonCardProps = {};

Import the PersonCard_person type from __generated__ folders:

1
import {PersonCard_person} from './__generated__/PersonCard_person.graphql';

Add the person inside the type PersonCardProps:

1
2
3
type PersonCardProps = {
  person: PersonCard_person;
};

On props arguments from PersonCard, type the component with the PersonCardProps:

1
const PersonCard = ({person}: PersonCardProps) => { ... }

The final result of both components should look like:

** PersonRenderer **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React from 'react';
import {graphql, QueryRenderer} from 'react-relay';
import {Environment} from '../../../relay';
import PersonCard from './PersonCard';
import {View, Text} from 'react-native';
import {PersonRendererQuery} from './__generated__/PersonRendererQuery.graphql';

const PersonRenderer = () => {
const renderPersons = (people: PersonRendererQuery['response']['people']) => {
    return people.edges.map(({node}) => <PersonCard person={node} />);
  };

  return (
    <QueryRenderer
      environment={Environment}
      query={graphql`
        query PersonRendererQuery {
          people {
            edges {
              node {
                ...PersonCard_person
              }
            }
          }
        }
      `}
      variables={null}
      render={({error, props}) => {
        if (error) {
          return (
            <View>
              <Text>{error.message}</Text>
            </View>
          );
        } else if (props) {
          return renderPersons(props.people);
        }
        return (
          <View>
            <Text>loading</Text>
          </View>
        );
      }}
    />
  );
};

export default PersonRenderer;

** PersonCard **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react';

import {createFragmentContainer, graphql} from 'react-relay';

import {View, Text} from 'react-native';
import {PersonCard_person} from './__generated__/PersonCard_person.graphql';

type PersonCardProps = {
  person: PersonCard_person;
};

const PersonCard = ({person}: PersonCardProps) => {
  return (
    <View>
      <Text>Name: {person.name}</Text>
      <Text>Salary: {person.salary}</Text>
    </View>
  );
};

export default createFragmentContainer(PersonCard, {
  person: graphql`
    fragment PersonCard_person on Person {
      id
      name
      salary
    }
  `,
});

Conclusion

The final result of QueryRenderer demonstrated how the application can be abstracted. The application can display the Person inside of the Query Renderer. As the PersonCard has more components, it doesn’t change the way the Query Renderer was built.

The PersonRenderer was built to show how a query can be done in easy steps, combined with the power of the Back4App server. In the next guide, you will learn how to retrieve a specific Person object and show their attributes.