React Native

Relational Query in React Native using Parse

Introduction

In this guide, you will perform relational queries in Parse and implement a React Native component using these queries. You will learn how to set up and query realistic data using Back4App and React Native.

Prerequisites

To complete this tutorial, you will need:

Goal

Query relational data stored on Back4App from a React Native App.

Step 1 - Understanding the Parse.Query class

Any Parse query operation uses the Parse.Query object type, which will help you retrieve specific data from your database throughout your app. It is crucial to know that a Parse.Query will only resolve after calling a retrieve method (like Parse.Query.find or Parse.Query.first), so a query can be set up and several modifiers can be chained before actually being called.

To create a new Parse.Query, you need to pass as a parameter the desired Parse.Object subclass, which is the one that will contain your query results. An example query can be seen below, in which a fictional Book subclass is being queried.

1
2
3
4
// This will create your query
let parseQuery = new Parse.Query("Book");
// The query will resolve only after calling this method
let queryResult = await parseQuery.find();

You can read more about the Parse.Query class here at the official documentation.

Step 2 - Save some data on Back4App

Let’s create an assortment of classes, which will be the target of our queries in this guide. The classes are: Author, Book, Publisher and BookStore, in which Book has a 1:N relation with Publisher and N:N with Author, and BookStore has an N:N relation with Book.

On Parse JS Console is possible to run JavaScript code directly, querying and updating your application database contents using the JS SDK commands. Run the code below from your JS Console and insert the data on Back4App. Here is how the JS console looks like in your dashboard:

React Native Back4App

Go ahead and create the classes with the following example content:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Add objects and create tables
// Authors
const AuthorA = new Parse.Object('Author');
AuthorA.set('name', 'Aaron Writer');
await AuthorA.save();

const AuthorB = new Parse.Object('Author');
AuthorB.set('name', 'Beatrice Novelist');
await AuthorB.save();

const AuthorC = new Parse.Object('Author');
AuthorC.set('name', 'Casey Columnist');
await AuthorC.save();

// Publishers
const PublisherA = new Parse.Object('Publisher');
PublisherA.set('name', 'Acacia Publishings');
await PublisherA.save();

const PublisherB = new Parse.Object('Publisher');
PublisherB.set('name', 'Birch Distributions');
await PublisherB.save();

// Books
const BookA = new Parse.Object('Book');
BookA.set('title', 'A Love Story');
BookA.set('publisher', PublisherA);
BookA.set('publishingDate', new Date('05/07/1998'));
const BookARelation = BookA.relation("authors");
BookARelation.add(AuthorA);
await BookA.save();

const BookB = new Parse.Object('Book');
BookB.set('title', 'Benevolent Elves');
BookB.set('publisher', PublisherB);
BookB.set('publishingDate', new Date('11/31/2008'));
const BookBRelation = BookB.relation("authors");
BookBRelation.add(AuthorB);
await BookB.save();

const BookC = new Parse.Object('Book');
BookC.set('title', 'Can You Believe It?');
BookC.set('publisher', PublisherB);
BookC.set('publishingDate', new Date('08/21/2018'));
const BookCRelation = BookC.relation("authors");
BookCRelation.add(AuthorA);
BookCRelation.add(AuthorC);
await BookC.save();

// BookStore
const BookStoreA = new Parse.Object('BookStore');
BookStoreA.set('name', 'Books of Love');
const BookStoreARelation = BookStoreA.relation("books");
BookStoreARelation.add(BookA);
await BookStoreA.save();

const BookStoreB = new Parse.Object('BookStore');
BookStoreB.set('name', 'Fantasy Books');
const BookStoreBRelation = BookStoreB.relation("books");
BookStoreBRelation.add(BookB);
await BookStoreB.save();

const BookStoreC = new Parse.Object('BookStore');
BookStoreC.set('name', 'General Books');
const BookStoreCRelation = BookStoreC.relation("books");
BookStoreCRelation.add(BookA);
BookStoreCRelation.add(BookC);
await BookStoreC.save();

console.log('Success');

Step 3 - Query the data

Now that you have populated all the classes, we can now perform some relational queries in it. Let’s begin by filtering Book results by the publisher, searching for the ones that belong to the Publisher “Acacia Publishings” (or “Publisher A”) using the basic Parse.Query.equalTo method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Get PublisherA object
const PublisherAQuery = new Parse.Query('Publisher');
PublisherAQuery.equalTo('name', 'Acacia Publishings');
const PublisherA = await PublisherAQuery.first();

// Query Books with PublisherA
const bookQuery = new Parse.Query('Book');
bookQuery.equalTo('publisher', PublisherA);
let queryResults = await bookQuery.find();

// Let's show the results
for (let result of queryResults) {
  // You access `Parse.Objects` attributes by using `.get`
  console.log(result.get('title'));
};

Let’s now query which BookStore objects contain Book objects with publishing date greater than 01/01/2010, using an inner query with the Parse.Query.greaterThan method and then the Parse.Query.matchesQuery method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create inner Book query
const bookQuery = new Parse.Query('Book');
bookQuery.greaterThan('publishingDate', new Date('01/01/2010'));

// Query BookStore using inner Book query
const bookStoreQuery = new Parse.Query('BookStore');
bookStoreQuery.matchesQuery('books', bookQuery);
let queryResults = await bookStoreQuery.find();

// Let's show the results
for (let result of queryResults) {
  // You access `Parse.Objects` attributes by using `.get`
  console.log(result.get('name'));
};

Let’s now create a more complex relational query, looking for BookStore objects that have at least one Book written by Author “Aaron Writer” (or “AuthorA”), using equalTo and matchesQuery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Get AuthorA object
const AuthorAQuery = new Parse.Query('Author');
AuthorAQuery.equalTo('name', 'Aaron Writer');
const AuthorA = await AuthorAQuery.first();

// Create inner Book query
const bookQuery = new Parse.Query('Book');
bookQuery.equalTo('authors', AuthorA);

// Query BookStore using inner Book query
const bookStoreQuery = new Parse.Query('BookStore');
bookStoreQuery.matchesQuery('books', bookQuery);
let queryResults = await bookStoreQuery.find();

// Let's show the results
for (let result of queryResults) {
  // You access `Parse.Objects` attributes by using `.get`
  console.log(result.get('name'));
};

Step 4 - Query from a React Native component

Let’s now use our example queries inside a component in React Native, with a simple interface having a list showing results and also buttons for calling the queries. This is how the component code is laid out, note the doQuery functions, containing the example code from before.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import React, {useState} from 'react';
import {Alert, Image, View, ScrollView, StyleSheet} from 'react-native';
import Parse from 'parse/react-native';
import {
  List,
  Title,
  Button as PaperButton,
  Text as PaperText,
} from 'react-native-paper';

export const QueryList = () => {
  // State variable
  const [queryResults, setQueryResults] = useState(null);

  const doQueryA = async function () {
    // Get PublisherA object
    const PublisherAQuery = new Parse.Query('Publisher');
    PublisherAQuery.equalTo('name', 'Acacia Publishings');
    const PublisherA = await PublisherAQuery.first();

    // Query Books with PublisherA
    const bookQuery = new Parse.Query('Book');
    bookQuery.equalTo('publisher', PublisherA);

    try {
      let results = await bookQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const doQueryB = async function () {
    // Create inner Book query
    const bookQuery = new Parse.Query('Book');
    bookQuery.greaterThan('publishingDate', new Date('01/01/2010'));

    // Query BookStore using inner Book query
    const bookStoreQuery = new Parse.Query('BookStore');
    bookStoreQuery.matchesQuery('books', bookQuery);

    try {
      let results = await bookStoreQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const doQueryC = async function () {
    // Get AuthorA object
    const AuthorAQuery = new Parse.Query('Author');
    AuthorAQuery.equalTo('name', 'Aaron Writer');
    const AuthorA = await AuthorAQuery.first();

    // Create inner Book query
    const bookQuery = new Parse.Query('Book');
    bookQuery.equalTo('authors', AuthorA);

    // Query BookStore using inner Book query
    const bookStoreQuery = new Parse.Query('BookStore');
    bookStoreQuery.matchesQuery('books', bookQuery);

    try {
      let results = await bookStoreQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const clearQueryResults = async function () {
    setQueryResults(null);
    return true;
  };

  return (
    <>
      <View style={Styles.header}>
        <Image
          style={Styles.header_logo}
          source={ {
            uri:
              'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png',
          } }
        />
        <PaperText style={Styles.header_text}>
          <PaperText style={Styles.header_text_bold}>
            {'React Native on Back4App - '}
          </PaperText>
          {' Relational Queries'}
        </PaperText>
      </View>
      <ScrollView style={Styles.wrapper}>
        <View>
          <Title>{'Result List'}</Title>
          {/* Query list */}
          {queryResults !== null &&
            queryResults !== undefined &&
            queryResults.map((result) => (
              <List.Item
                key={result.id}
                title={
                  result.get('name') !== undefined
                    ? result.get('name')
                    : result.get('title')
                }
                titleStyle={Styles.list_text}
                style={Styles.list_item}
              />
            ))}
          {queryResults === null ||
          queryResults === undefined ||
          (queryResults !== null &&
            queryResults !== undefined &&
            queryResults.length <= 0) ? (
            <PaperText>{'No results here!'}</PaperText>
          ) : null}
        </View>
        <View>
          <Title>{'Query buttons'}</Title>
          <PaperButton
            onPress={() => doQueryA()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query A'}
          </PaperButton>
          <PaperButton
            onPress={() => doQueryB()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query B'}
          </PaperButton>
          <PaperButton
            onPress={() => doQueryC()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query C'}
          </PaperButton>
          <PaperButton
            onPress={() => clearQueryResults()}
            mode="contained"
            icon="delete"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Clear Results'}
          </PaperButton>
        </View>
      </ScrollView>
    </>
  );
};

// These define the screen component styles
const Styles = StyleSheet.create({
  header: {
    alignItems: 'center',
    paddingTop: 30,
    paddingBottom: 50,
    backgroundColor: '#208AEC',
  },
  header_logo: {
    height: 50,
    width: 220,
    resizeMode: 'contain',
  },
  header_text: {
    marginTop: 15,
    color: '#f0f0f0',
    fontSize: 16,
  },
  header_text_bold: {
    color: '#fff',
    fontWeight: 'bold',
  },
  wrapper: {
    width: '90%',
    alignSelf: 'center',
  },
  list_button: {
    marginTop: 6,
    marginLeft: 15,
    height: 40,
  },
  list_item: {
    borderBottomWidth: 1,
    borderBottomColor: 'rgba(0, 0, 0, 0.12)',
  },
  list_text: {
    fontSize: 15,
  },
});
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import React, {FC, ReactElement, useState} from 'react';
import {Alert, Image, View, ScrollView, StyleSheet} from 'react-native';
import Parse from 'parse/react-native';
import {
  List,
  Title,
  Button as PaperButton,
  Text as PaperText,
} from 'react-native-paper';

export const QueryList: FC<{}> = ({}): ReactElement => {
  // State variable
  const [queryResults, setQueryResults] = useState(null);

  const doQueryA = async function (): Promise<boolean> {
    // Get PublisherA object
    const PublisherAQuery: Parse.Query = new Parse.Query('Publisher');
    PublisherAQuery.equalTo('name', 'Acacia Publishings');
    const PublisherA: Parse.Object = await PublisherAQuery.first();

    // Query Books with PublisherA
    const bookQuery: Parse.Query = new Parse.Query('Book');
    bookQuery.equalTo('publisher', PublisherA);

    try {
      let results: [Parse.Object] = await bookQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const doQueryB = async function (): Promise<boolean> {
    // Create inner Book query
    const bookQuery: Parse.Query = new Parse.Query('Book');
    bookQuery.greaterThan('publishingDate', new Date('01/01/2010'));

    // Query BookStore using inner Book query
    const bookStoreQuery: Parse.Query = new Parse.Query('BookStore');
    bookStoreQuery.matchesQuery('books', bookQuery);

    try {
      let results: [Parse.Object] = await bookStoreQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const doQueryC = async function (): Promise<boolean> {
    // Get AuthorA object
    const AuthorAQuery: Parse.Query = new Parse.Query('Author');
    AuthorAQuery.equalTo('name', 'Aaron Writer');
    const AuthorA: Parse.Object = await AuthorAQuery.first();

    // Create inner Book query
    const bookQuery: Parse.Query = new Parse.Query('Book');
    bookQuery.equalTo('authors', AuthorA);

    // Query BookStore using inner Book query
    const bookStoreQuery: Parse.Query = new Parse.Query('BookStore');
    bookStoreQuery.matchesQuery('books', bookQuery);

    try {
      let results: [Parse.Object] = await bookStoreQuery.find();
      setQueryResults(results);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const clearQueryResults = async function (): Promise<boolean> {
    setQueryResults(null);
    return true;
  };

  return (
    <>
      <View style={Styles.header}>
        <Image
          style={Styles.header_logo}
          source={ {
            uri:
              'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png',
          } }
        />
        <PaperText style={Styles.header_text}>
          <PaperText style={Styles.header_text_bold}>
            {'React Native on Back4App - '}
          </PaperText>
          {' Relational Queries'}
        </PaperText>
      </View>
      <ScrollView style={Styles.wrapper}>
        <View>
          <Title>{'Result List'}</Title>
          {/* Query list */}
          {queryResults !== null &&
            queryResults !== undefined &&
            queryResults.map((result: Parse.Object) => (
              <List.Item
                key={result.id}
                title={
                  result.get('name') !== undefined
                    ? result.get('name')
                    : result.get('title')
                }
                titleStyle={Styles.list_text}
                style={Styles.list_item}
              />
            ))}
          {queryResults === null ||
          queryResults === undefined ||
          (queryResults !== null &&
            queryResults !== undefined &&
            queryResults.length <= 0) ? (
            <PaperText>{'No results here!'}</PaperText>
          ) : null}
        </View>
        <View>
          <Title>{'Query buttons'}</Title>
          <PaperButton
            onPress={() => doQueryA()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query A'}
          </PaperButton>
          <PaperButton
            onPress={() => doQueryB()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query B'}
          </PaperButton>
          <PaperButton
            onPress={() => doQueryC()}
            mode="contained"
            icon="search-web"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Query C'}
          </PaperButton>
          <PaperButton
            onPress={() => clearQueryResults()}
            mode="contained"
            icon="delete"
            color={'#208AEC'}
            style={Styles.list_button}>
            {'Clear Results'}
          </PaperButton>
        </View>
      </ScrollView>
    </>
  );
};

// These define the screen component styles
const Styles = StyleSheet.create({
  header: {
    alignItems: 'center',
    paddingTop: 30,
    paddingBottom: 50,
    backgroundColor: '#208AEC',
  },
  header_logo: {
    height: 50,
    width: 220,
    resizeMode: 'contain',
  },
  header_text: {
    marginTop: 15,
    color: '#f0f0f0',
    fontSize: 16,
  },
  header_text_bold: {
    color: '#fff',
    fontWeight: 'bold',
  },
  wrapper: {
    width: '90%',
    alignSelf: 'center',
  },
  list_button: {
    marginTop: 6,
    marginLeft: 15,
    height: 40,
  },
  list_item: {
    borderBottomWidth: 1,
    borderBottomColor: 'rgba(0, 0, 0, 0.12)',
  },
  list_text: {
    fontSize: 15,
  },
});

This is how the component should look like after rendering and querying by one of the query functions:

React Native Back4App

Conclusion

At the end of this guide, you learned how relational queries work on Parse and how to perform them on Back4App from a React Native App. In the next guide, you will learn how to work with Users in Parse.