React Native

Query in React Native using Parse

Introduction

One of the most important features in many applications is the ability to find specific data in a fast and straightforward way. Parse has great tools for data querying, such as comparing and ordering results, while being able to chain and combine queries into more complex ones.

In this guide, you will implement a React Native book registration application that contains a listing screen powered with several querying options. You will learn how to set up and query realistic data using your Back4App and React Native.

At any time, you can access this project via our GitHub repositories to checkout the styles and complete code.

Prerequisites

To complete this tutorial, you will need:

Goal

Show how basic data queries work and how to perform them in Parse.

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.get), 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
const parseQuery = new Parse.Query("Book");
// The query will resolve only after calling this method
const queryResult = await parseQuery.find();
1
2
3
4
// This will create your query
const parseQuery: Parse.Query = new Parse.Query("Book");
// The query will resolve only after calling this method
const queryResult: Parse.Object[] = await parseQuery.find();

In this guide we will take a look at the following Parse.Query methods:

  • equalTo: this comparison is the most common one, in which an exact value is queried, can be used in most data types;
  • greaterThanOrEqualTo and lessThanOrEqualTo: logical comparisons used mainly in number and Date attributes;
  • contains: query objects by a string (text) attribute, checking if the searched text is contained by the attribute value;
  • addAscending and addDescending: basic list ordering methods;

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

Step 2 - Understanding the Book class

Since we will be using a book registration application example in this guide, you need first to understand how the object relations are laid out in this database. The main object class you’ll be using is the Book class, storing each book entry in the registration. Also, these are the other four object classes:

  • Publisher: book publisher name, one-to-many relation with Book;
  • Genre: book genre, one-to-many relation with Book. Note that for this example we will consider that a book can only have one genre;
  • Author: book author, many-to-many relation with Book, since a book can have more than one author and an author can have more than one book as well;
  • ISDB: book ISDB identifying number, one-to-one relation with Book, since this number is unique for each book.

Each of these object classes has only a string type name attribute (title for the Book), apart from any relational attribute, in exception to Book, which has a number type year attribute in addition to that.

Step 3 - Performing Basic Queries

Before going into this step we recommend you to clone and run the React Native Library app exmaple (JavaScript Example Repository, TypeScript Example Repository). This application has two main screens: one responsible for listing the registered books and the other for creating new books.

Our application querying is done inside the listing component, with the following query possibilities:

  • ascending or descending order by Book title;
  • Book publishing year interval using greaterThanOrEqualTo and/or lessThanOrEqualTo;
  • Book title text search using contains;
  • Book relations query using equalTo for filtering by genre, authors, and/or publisher;

Let’s now take a look at how the querying selection fields are laid out in the book listing screen user interface:

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
<View>
  <List.Accordion title="Query options">
    {/* Ascending and descending ordering by title */}
    <RadioButton.Group
      onValueChange={newValue => setQueryOrdering(newValue)}
      value={queryOrdering}>
      <List.Accordion title="Ordering">
        <RadioButton.Item
          key={'ascending'}
          label={'Title A-Z'}
          value={'ascending'}
        />
        <RadioButton.Item
          key={'descending'}
          label={'Title Z-A'}
          value={'descending'}
        />
      </List.Accordion>
    </RadioButton.Group>
    {/* Title text search */}
    <PaperTextInput
      value={queryTitle}
      onChangeText={text => setQueryTitle(text)}
      label="Book title"
      mode="outlined"
      autoCapitalize={'none'}
      style={Styles.form_input}
    />
    {/* Publishing year interval */}
    <List.Accordion title="Publishing Year">
      <PaperTextInput
        value={queryYearFrom}
        onChangeText={text => setQueryYearFrom(text)}
        label="Year from"
        mode="outlined"
        style={Styles.form_input}
      />
      <PaperTextInput
        value={queryYearTo}
        onChangeText={text => setQueryYearTo(text)}
        label="Year to"
        mode="outlined"
        style={Styles.form_input}
      />
    </List.Accordion>
    {/* Publisher filter */}
    {publishers !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryPublisher(newValue)}
        value={queryPublisher}>
        <List.Accordion title="Publisher">
          {publishers.map((publisher, index) => (
            <RadioButton.Item
              key={`${index}`}
              label={publisher.get('name')}
              value={publisher}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* Genre filter */}
    {genres !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryGenre(newValue)}
        value={queryGenre}>
        <List.Accordion title="Genre">
          {genres.map((genre, index) => (
            <RadioButton.Item
              key={`${index}`}
              label={genre.get('name')}
              value={genre}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* Authors filter */}
    {authors !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryAuthor(newValue)}
        value={queryAuthor}>
        <List.Accordion title="Author">
          {authors.map((author, index) => (
            <RadioButton.Item
              key={`${index}`}
              label={author.get('name')}
              value={author}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* ISBDs filter */}
    {isbds !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryIsbd(newValue)}
        value={queryIsbd}>
        <List.Accordion title="ISBD">
          {isbds.map((isbd, index) => (
            <RadioButton.Item
              key={`${index}`}
              label={isbd.get('name')}
              value={isbd}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
  </List.Accordion>
  <PaperButton
    onPress={() => queryBooks()}
    mode="contained"
    icon="search-web"
    color={'#208AEC'}
    style={Styles.create_button}>
    {'Query'}
  </PaperButton>
  <PaperButton
    onPress={() => clearQueryChoices()}
    mode="contained"
    icon="delete"
    color={'#208AEC'}
    style={Styles.create_button}>
    {'Clear Query'}
  </PaperButton>
</View>
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
<View>
  <List.Accordion title="Query options">
    {/* Ascending and descending ordering by title */}
    <RadioButton.Group
      onValueChange={newValue => setQueryOrdering(newValue)}
      value={queryOrdering}>
      <List.Accordion title="Ordering">
        <RadioButton.Item
          key={'ascending'}
          label={'Title A-Z'}
          value={'ascending'}
        />
        <RadioButton.Item
          key={'descending'}
          label={'Title Z-A'}
          value={'descending'}
        />
      </List.Accordion>
    </RadioButton.Group>
    {/* Title text search */}
    <PaperTextInput
      value={queryTitle}
      onChangeText={text => setQueryTitle(text)}
      label="Book title"
      mode="outlined"
      autoCapitalize={'none'}
      style={Styles.form_input}
    />
    {/* Publishing year interval */}
    <List.Accordion title="Publishing Year">
      <PaperTextInput
        value={queryYearFrom}
        onChangeText={text => setQueryYearFrom(text)}
        label="Year from"
        mode="outlined"
        style={Styles.form_input}
      />
      <PaperTextInput
        value={queryYearTo}
        onChangeText={text => setQueryYearTo(text)}
        label="Year to"
        mode="outlined"
        style={Styles.form_input}
      />
    </List.Accordion>
    {/* Publisher filter */}
    {publishers !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryPublisher(newValue)}
        value={queryPublisher}>
        <List.Accordion title="Publisher">
          {publishers.map((publisher: Parse.Object, index: number) => (
            <RadioButton.Item
              key={`${index}`}
              label={publisher.get('name')}
              value={publisher}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* Genre filter */}
    {genres !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryGenre(newValue)}
        value={queryGenre}>
        <List.Accordion title="Genre">
          {genres.map((genre: Parse.Object, index: number) => (
            <RadioButton.Item
              key={`${index}`}
              label={genre.get('name')}
              value={genre}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* Authors filter */}
    {authors !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryAuthor(newValue)}
        value={queryAuthor}>
        <List.Accordion title="Author">
          {authors.map((author: Parse.Object, index: number) => (
            <RadioButton.Item
              key={`${index}`}
              label={author.get('name')}
              value={author}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
    {/* ISBDs filter */}
    {isbds !== null && (
      <RadioButton.Group
        onValueChange={newValue => setQueryIsbd(newValue)}
        value={queryIsbd}>
        <List.Accordion title="ISBD">
          {isbds.map((isbd: Parse.Object, index: number) => (
            <RadioButton.Item
              key={`${index}`}
              label={isbd.get('name')}
              value={isbd}
            />
          ))}
        </List.Accordion>
      </RadioButton.Group>
    )}
  </List.Accordion>
  <PaperButton
    onPress={() => queryBooks()}
    mode="contained"
    icon="search-web"
    color={'#208AEC'}
    style={Styles.create_button}>
    {'Query'}
  </PaperButton>
  <PaperButton
    onPress={() => clearQueryChoices()}
    mode="contained"
    icon="delete"
    color={'#208AEC'}
    style={Styles.create_button}>
    {'Clear Query'}
  </PaperButton>
</View>

Here is how the list screen looks like after rendering and opening these query options accordions:

React Native Back4App

The queryBooks function handles the selected query field values and performs the query. Here is the code for this function, pay close attention to how every query option is chained and there is only one retrieve method call (Parse.Query.find) at the end:

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
const queryBooks = async function () {
  // These values are simple input or radio buttons with query choices
  // linked to state variables
  const queryOrderingValue = queryOrdering;
  const queryTitleValue = queryTitle;
  const queryYearFromValue = Number(queryYearFrom);
  const queryYearToValue = Number(queryYearTo);

  // These values also come from state variables linked to
  // the screen query fields, with its options being every
  // parse object instance saved on server from the referred class, which is
  // queried on screen load via useEffect; this variables retrieve the user choices
  // as a complete Parse.Object;
  const queryPublisherValue = queryPublisher;
  const queryGenreValue = queryGenre;
  const queryAuthorValue = queryAuthor;
  const queryIsbdValue = queryIsbd;

  // Create our Parse.Query instance so methods can be chained
  // Reading parse objects is done by using Parse.Query
  const parseQuery = new Parse.Query('Book');

  // Basic queries
  // Ordering (two options)
  if (queryOrderingValue === 'ascending') {
    parseQuery.addAscending('title');
  } else if (queryOrderingValue === 'descending') {
    parseQuery.addDescending('title');
  }
  // Title query
  if (queryTitleValue !== '') {
    // Be aware that contains is case sensitive
    parseQuery.contains('title', queryTitleValue);
  }
  // Year interval query
  if (queryYearFromValue !== 0 || queryYearToValue !== 0) {
    if (queryYearFromValue !== 0) {
      parseQuery.greaterThanOrEqualTo('year', queryYearFromValue);
    }
    if (queryYearToValue !== 0) {
      parseQuery.lessThanOrEqualTo('year', queryYearToValue);
    }
  }

  // Association queries
  // One-to-many queries
  if (queryPublisherValue !== '') {
    parseQuery.equalTo('publisher', queryPublisherValue);
  }
  if (queryGenreValue !== '') {
    parseQuery.equalTo('genre', queryGenreValue);
  }

  // One-to-one query
  if (queryIsbdValue !== '') {
    parseQuery.equalTo('isbd', queryIsbdValue);
  }

  // Many-to-many query
  // In this case, we need to retrieve books related to the chosen author
  if (queryAuthorValue !== '') {
    parseQuery.equalTo('authors', queryAuthorValue);
  }

  try {
    let books = await parseQuery.find();
    // Many-to-many objects retrieval
    // In this example we need to get every related author Parse.Object
    // and add it to our query result objects
    for (let book of books) {
      // This query is done by creating a relation and querying it
      let bookAuthorsRelation = book.relation('authors');
      book.authorsObjects = await bookAuthorsRelation.query().find();
    }
    setQueriedBooks(books);
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  }
};
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
const queryBooks = async function (): Promise<boolean> {
  // These values are simple input or radio buttons with query choices
  // linked to state variables
  const queryOrderingValue: string = queryOrdering;
  const queryTitleValue: string = queryTitle;
  const queryYearFromValue: number = Number(queryYearFrom);
  const queryYearToValue: number = Number(queryYearTo);

  // These values also come from state variables linked to
  // the screen query fields, with its options being every
  // parse object instance saved on server from the referred class, which is
  // queried on screen load via useEffect; this variables retrieve the user choices
  // as a complete Parse.Object;
  const queryPublisherValue: Parse.Object = queryPublisher;
  const queryGenreValue: Parse.Object = queryGenre;
  const queryAuthorValue: Parse.Object = queryAuthor;
  const queryIsbdValue: Parse.Object = queryIsbd;

  // Create our Parse.Query instance so methods can be chained
  // Reading parse objects is done by using Parse.Query
  const parseQuery: Parse.Query = new Parse.Query('Book');

  // Basic queries
  // Ordering (two options)
  if (queryOrderingValue === 'ascending') {
    parseQuery.addAscending('title');
  } else if (queryOrderingValue === 'descending') {
    parseQuery.addDescending('title');
  }
  // Title query
  if (queryTitleValue !== '') {
    // Be aware that contains is case sensitive
    parseQuery.contains('title', queryTitleValue);
  }
  // Year interval query
  if (queryYearFromValue !== 0 || queryYearToValue !== 0) {
    if (queryYearFromValue !== 0) {
      parseQuery.greaterThanOrEqualTo('year', queryYearFromValue);
    }
    if (queryYearToValue !== 0) {
      parseQuery.lessThanOrEqualTo('year', queryYearToValue);
    }
  }

  // Association queries
  // One-to-many queries
  if (queryPublisherValue !== '') {
    parseQuery.equalTo('publisher', queryPublisherValue);
  }
  if (queryGenreValue !== '') {
    parseQuery.equalTo('genre', queryGenreValue);
  }

  // One-to-one query
  if (queryIsbdValue !== '') {
    parseQuery.equalTo('isbd', queryIsbdValue);
  }

  // Many-to-many query
  // In this case, we need to retrieve books related to the chosen author
  if (queryAuthorValue !== '') {
    parseQuery.equalTo('authors', queryAuthorValue);
  }

  try {
    let books: [Parse.Object] = await parseQuery.find();
    // Many-to-many objects retrieval
    // In this example we need to get every related author Parse.Object
    // and add it to our query result objects
    for (let book of books) {
      // This query is done by creating a relation and querying it
      let bookAuthorsRelation = book.relation('authors');
      book.authorsObjects = await bookAuthorsRelation.query().find();
    }
    setQueriedBooks(books);
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  }
};

Let’s now perform a query using this function and choices, assuming that the user wants to filter books from years 2015-2020, containing the word “peanut” in its title and from the “Acacia” publisher. On top of that, the user also wants to order the results in descending order. That’s how the complete list of books looks like before querying:

React Native Back4App

Now after querying:

React Native Back4App

Conclusion

At the end of this guide, you learned how basic data queries work and how to perform them in Parse on React Native. In the next guide, you will learn about all of querying possibilities and methods in Parse.