React Native

One to one Relationship

Introduction

At the core of many backends, you will find the ability to store data. Using Parse, you can store data objects establishing relations between them. Data relations standardize how each data object is related or associated with other ones. That can give you extra power when building and running complex queries. There are three main relation types:

  • one-to-many, where one object can be related to many other objects;
  • one-to-one, establishing direct relations between two objects and only them;
  • many-to-many, which can create many complex relations between many objects.

In this guide, we will focus on one-to-one relations. These relations are common in applications involving sensible data and user management which require unique fields that need to be enforced, such as ID numbers and phone numbers. Data storage backends usually will demand explicit declarations of these associations and Parse does not have an automatic solution for achieving such associations.

However, there are ways to implement 1:1 relations in Parse by using Parse.Cloud functions on your server, enforcing that table relations remain unique before saving data. This is done by creating beforeSave functions in both related classes and prevent saving if the father class already possesses one instance in the child class, and vice-versa.

You can also treat these cases in your application Parse code, querying the server before saving and guaranteeing said relation. This will be the way shown in this guide, but note that using cloud functions is much cleaner and more advised.

In this guide, you will implement a React Native book registration application that contains the three main kinds of data associations. You will learn how to create and query one-to-one data relations using 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

To perform and demonstrate one-to-one database relations in React Native using Parse in a realistic scenario.

Step 1 - Understanding the Book class

Since in this guide we will be using a book registration application example, you need to first understand how the object relations are laid out in this database. The main object class that you’ll be using is the Book class, which will store each book entry in the registration. 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.

Here is a visual representation of these database tables:

React Native Back4App

For simplicity’s, we will assume that each object class has only a string type name attribute (title for the Book), apart from any additional relational attribute.

Step 2 - Creating one-to-one relations

Before going into this step we recommend you to clone and run the React Native Library app example (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. In the book registration form, there are direct links to the other related objects and a TextInput field assigned to the book’s ISBD value, which will be used to create your one-to-one relation.

React Native Back4App

Let’s take a look at the book creation method that is called when submitting this form:

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
const createBook = async function () {
  try {
    // These values come from state variables linked to
    // the screen form fields, retrieving the user choices
    // as a complete Parse.Object, when applicable;
    const bookTitleValue = bookTitle;
    const bookISBDValue = bookISBD;
    // For example, bookPublisher holds the value from
    // RadioButton.Group field with its options being every
    // Publisher parse object instance saved on server, which is
    // queried on screen load via useEffect
    const bookPublisherObject = bookPublisher;
    const bookGenreObject = bookGenre;
    // bookAuthors can be an array of Parse.Objects, since the book
    // may have more than one Author
    const bookAuthorsObjects = bookAuthors;

    // Creates a new parse object instance
    let Book = new Parse.Object('Book');

    // Set data to parse object
    // Simple title field
    Book.set('title', bookTitleValue);

    // 1:1 relation, need to check for uniqueness of value before creating a new ISBD object
    let isbdQuery = new Parse.Query('ISBD');
    isbdQuery.equalTo('name', bookISBDValue);
    let isbdQueryResult = await isbdQuery.first();
    if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
      // If first returns a valid object instance, it means that there
      // is at least one instance of ISBD with the informed value
      Alert.alert(
        'Error!',
        'There is already an ISBD instance with this value!',
      );
      return false;
    } else {
      // Create a new ISBD object instance to create a one-to-one relation on saving
      let ISBD = new Parse.Object('ISBD');
      ISBD.set('name', bookISBDValue);
      ISBD = await ISBD.save();
      // Set the new object to the new book object ISBD field
      Book.set('isbd', ISBD);
    }

    // One-to-many relations can be set in two ways:
    // add direct object to field (Parse will convert to pointer on save)
    Book.set('publisher', bookPublisherObject);
    // or add pointer to field
    Book.set('genre', bookGenreObject.toPointer());

    // many-to-many relation
    // Create a new relation so data can be added
    let authorsRelation = Book.relation('authors');
    // bookAuthorsObjects is an array of Parse.Objects,
    // you can add to relation by adding the whole array or object by object
    authorsRelation.add(bookAuthorsObjects);

    // After setting the values, save it on the server
    try {
      await Book.save();
      // Success
      Alert.alert('Success!');
      navigation.goBack();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  } catch (error) {
    // Error can be caused by lack of value selection
    Alert.alert(
      'Error!',
      'Make sure to select valid choices in Publisher, Genre and Author fields!',
    );
    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
const createBook = async function (): Promise<boolean> {
  try {
    // These values come from state variables linked to
    // the screen form fields, retrieving the user choices
    // as a complete Parse.Object, when applicable;
    const bookTitleValue: string = bookTitle;
    const bookISBDValue: string = bookISBD;
    // For example, bookPublisher holds the value from
    // RadioButton.Group field with its options being every
    // Publisher parse object instance saved on server, which is
    // queried on screen load via useEffect
    const bookPublisherObject: Parse.Object = bookPublisher;
    const bookGenreObject: Parse.Object = bookGenre;
    // bookAuthors can be an array of Parse.Objects, since the book
    // may have more than one Author
    const bookAuthorsObjects: [Parse.Object] = bookAuthors;

    // Creates a new parse object instance
    let Book: Parse.Object = new Parse.Object('Book');

    // Set data to parse object
    // Simple title field
    Book.set('title', bookTitleValue);

    // 1:1 relation, need to check for uniqueness of value before creating a new ISBD object
    let isbdQuery: Parse.Query = new Parse.Query('ISBD');
    isbdQuery.equalTo('name', bookISBDValue);
    let isbdQueryResult: Parse.Object = await isbdQuery.first();
    if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
      // If first returns a valid object instance, it means that there
      // is at least one instance of ISBD with the informed value
      Alert.alert(
        'Error!',
        'There is already an ISBD instance with this value!',
      );
      return false;
    } else {
      // Create a new ISBD object instance to create a one-to-one relation on saving
      let ISBD: Parse.Object = new Parse.Object('ISBD');
      ISBD.set('name', bookISBDValue);
      ISBD = await ISBD.save();
      // Set the new object to the new book object ISBD field
      Book.set('isbd', ISBD);
    }

    // One-to-many relations can be set in two ways:
    // add direct object to field (Parse will convert to pointer on save)
    Book.set('publisher', bookPublisherObject);
    // or add pointer to field
    Book.set('genre', bookGenreObject.toPointer());

    // many-to-many relation
    // Create a new relation so data can be added
    let authorsRelation = Book.relation('authors');
    // bookAuthorsObjects is an array of Parse.Objects,
    // you can add to relation by adding the whole array or object by object
    authorsRelation.add(bookAuthorsObjects);

    // After setting the values, save it on the server
    try {
      await Book.save();
      // Success
      Alert.alert('Success!');
      navigation.goBack();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  } catch (error) {
    // Error can be caused by lack of value selection
    Alert.alert(
      'Error!',
      'Make sure to select valid choices in Publisher, Genre and Author fields!',
    );
    return false;
  }
};

Look how the bookISBDValue is set to the new book Parse.Object instance. Creating and saving one-to-one and one-to-many relations in Parse are similar processes, in which you pass as an argument the Parse.Object instance using the Parse.Object.set method, which takes two arguments: the field name and the value to be set.

The catch here is that, before saving, you need to enforce that there are no ISBD objects containing the informed ISBD ID string value in your database and that there are no Book objects already related to it as well. The second part will always be true in this case, since you are creating a new Book object every time. Enforcing the ISBD uniqueness can be achieved by using the following highlighted query:

1
2
3
4
5
6
7
8
9
10
11
12
let isbdQuery = new Parse.Query('ISBD');
isbdQuery.equalTo('name', bookISBDValue);
let isbdQueryResult = await isbdQuery.first();
if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
  // If first returns a valid object instance, it means that there
  // is at least one instance of ISBD with the informed value
  Alert.alert(
    'Error!',
    'There is already an ISBD instance with this value!',
  );
  return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
let isbdQuery: Parse.Query = new Parse.Query('ISBD');
isbdQuery.equalTo('name', bookISBDValue);
let isbdQueryResult: Parse.Object = await isbdQuery.first();
if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
  // If first returns a valid object instance, it means that there
  // is at least one instance of ISBD with the informed value
  Alert.alert(
    'Error!',
    'There is already an ISBD instance with this value!',
  );
  return false;
}

After successfully saving your objects, Parse will create a pointer data type column and a direct link on your dashboard for quick access under the hood.

React Native Back4App

Step 3 - Querying one-to-one relations

Querying one-to-one related objects is pretty straightforward as much of it is handled by Parse. Take a look at the query function in the book registers list screen:

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
const queryBooks = async function () {
  // These values come from state variables linked to
  // the screen query RadioButton.Group fields, with its options being every
  // parse object instance saved on server from the referred class, which is
  // queried on screen load via useEffect; these variables retrievie the user choices
  // as a complete Parse.Object;
  const queryPublisherValue = queryPublisher;
  const queryGenreValue = queryGenre;
  const queryAuthorValue = queryAuthor;
  const queryIsbdValue = queryIsbd;

  // Reading parse objects is done by using Parse.Query
  const parseQuery = new Parse.Query('Book');

  // 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
const queryBooks = async function (): Promise<boolean> {
  // These values come from state variables linked to
  // the screen query RadioButton.Group fields, with its options being every
  // parse object instance saved on server from the referred class, which is
  // queried on screen load via useEffect; these variables retrievie 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;

  // Reading parse objects is done by using Parse.Query
  const parseQuery: Parse.Query = new Parse.Query('Book');

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

In this case, to query any books related to a specific ISBD, you need only to perform a Parse.Query.equalTo method passing the Parse.Object instance as the parameter. After querying, Parse will store inside the resulting objects the complete instances of any one-to-one relational fields. To retrieve and show data from these object instances, you can chain the Parse.Object.get method like this: bookParseObject.get(('isbd').get('name'). Here is how the list screen looks like by using these getters to retrieve the ISBD name from the list items:

React Native Back4App

Conclusion

At the end of this guide, you learned how to create and query one-to-one relations in Parse on React Native. In the next guide, we will show you how to register users.