Docs Menu

Docs HomeRealm

React Native Tutorial

On this page

You can use the Realm JavaScript SDK, and the official Realm React library to build a mobile application with React Native. This tutorial walks you through how to build your own app that uses flexible sync.

For this tutorial, we'll start with a pre-built template application to see how everything fits together. Then, you will add a new Priority field to the existing Item model and update the Flexible Sync Subscription to only show items within a range of priorities.

This tutorial should take around 30 minutes.

Note

Check Out the Quick Start

If you prefer to explore on your own rather than follow a guided tutorial, check out the React Native Quick Start. It includes copyable code examples and the essential information that you need to set up a React Native app with Atlas Device Sync.

You must set up your local environment for React Native development before you can begin this tutorial. For detailed instructions, see Setting up the development environment in the React Native docs.

To start, you'll create a new app on your computer and get familiar with it. The app will use a pre-built template that includes a working React Native app and its corresponding Atlas App configuration files.

The template app is a basic "to-do list" application. Users can input a list of tasks that they need to do and then check each to-do off when they finish it. Some basic functionality is already set up for you:

  • Users can create email/password accounts and log in and out of the app.

  • Users can create, read, update, and delete Item objects that represent individual items to do.

  • The Item class is set up to automatically sync to Atlas and other clients with Atlas Device Sync.

Use the following steps to get the template app up and running on your computer:

1

Run the following command in a terminal to create a new folder with your templated configuration files and React Native app:

npx mongodb-realm-cli apps create \
--template react-native.todo.flex \
--name realm-sync-tutorial

Tip

The command creates a new folder in your current directory with the same name as the value of the --name flag.

2

In your terminal, run the following commands to navigate to the react native app and install its dependencies:

cd realm-sync-tutorial/frontend/react-native.todo.flex
npm install

To build and run the app on an iOS device or simulator, install the additional iOS dependencies with CocoaPods.

npx pod-install

Tip

Make sure to switch back to the frontend project's root directory after you install the pods.

3

At this point, you should have a fully functional React Native app that can run on both iOS and Android.

Tip

If you're encountering an error or otherwise having issues, it's likely an issue with how your local environment is set up. Check out the official React Native development environment setup guide and ensure that you've followed all the steps for your Development OS and Target OS.

4

When the build completes, you should have a functional app running on your simulator. In the app, register a new account and play around with the app for a few minutes:

  • Add a few to-do items to the list.

  • Press the checkbox on one or two items to mark them as complete.

  • Press the X on an item to delete it from the list.

If you connect to your Atlas cluster and query the todo.Item collection you can see your app's data. New data and changes from the React Native app automatically sync to this collection.

Tip

To learn how to connect to your Atlas cluster, see Connect to a Database Deployment.

Similarly, any changes in the collection automatically sync down to the React Native app. Try changing an item's completion status in your cluster - the React Native app will automatically update with the new value whenever there is a network connection available.

db.Item.updateOne(
{ "_id": "630665dba3f53b578b75bd7e" },
{ "$set": { "isComplete": true }
})

Tip

To learn more about updating data in your cluster, see Update Documents.

Screenshots of the welcome and to-do list screens from the template app.

Now that you have the template app running let's dive into the code to see what we're working with.

The template app includes a fully configured Atlas App in the backend directory. It has a unique app_id value in realm_config.json that client applications use to connect.

It also includes the following pre-defined configurations:

  • A data source linked to your Atlas Cluster.

  • A schema for the todo.Item collection that matches the Item class in the React Native app.

  • An authentication provider that lets users register for and log in to your app with an email and password.

  • A flexible sync configuration with a single session role that allows users to read and write only their own data.

The React Native app is a fully configured mobile client that can run on iOS and Android devices. It is located in the frontend/react-native.todo.flex directory.

The app uses the official @realm/react library. The library includes React hooks and components that let you connect to your Atlas App, read and write data, and automatically sync changes from other devices.

The app contains some configuration files and directories, but you can ignore those unless you want to customize the app. For this tutorial, you'll want to be familiar with the React components in the src/ directory:

File Name
Description
ItemSchema.js
The Item class, including its object schema. We import the class in RealmContext.js to include it in the app's Realm schema.
RealmContext.js
The Realm hooks and providers that we create and export to work with Realm throughout the app.
App.js
The root component of the app, which we wrap with the Realm hook providers from RealmContext.js.
WelcomeView.js
The user registration and login form that users see when they first open the app.
ItemListView.js
The main "to-do list" app that users interact with after they log in. It queries for Item objects from Realm and displays them in a list. It also includes the code to create new Item objects and store them in Realm.
CreateToDoPrompt.js
A UI form that lets us enter data for new Item objects. The code that actually creates new objects is in ItemListView.js.
LogoutButton.js
A reusable button that logs out an authenticated user.

Now that you're more familiar with what's already provided in the template app, let's write some code to implement a new feature.

For this tutorial, we'll add a new "priority" property to each of the Item objects. This will let us organize to-dos by how important they are and allow us to focus only on the most important ones.

1

We want to allow a small number of named priority levels, and we want to easily be able sort the levels. To do this, we'll use a helper function to define an "enum" object that maps a set of ordered level names to and from an integer that represents their priority.

Add the following code directly under the import statements in src/ItemSchema.js:

ItemSchema.js
function createEnum(arr) {
arr.forEach((p, i) => arr[p] = i);
return arr;
}
// Priority.High === 1
// Priority[Priority.High] === "High"
export const Priority = createEnum([
"Severe",
"High",
"Medium",
"Low",
])

The priority levels in the enum are ordered from most important to least. The corresponding index value for each level increases from the most important, Priority[0], to the least important, Priority[3]. This means that a "higher" priority level (i.e. a more important one) has a lower index value.

Tip

In a TypeScript app, you could define an enum to model this instead of using the helper function:

// Priority.High === 1
// Priority[Priority.High] === "High"
export enum Priority {
Severe = 0,
High = 1,
Medium = 2,
Low = 3,
}
2

Now we have an enum that defines the possible values of the priority field. However, we still have to define the priority field in the Item class.

Add the following lines to your code in src/ItemSchema.js to add priority to the model:

ItemSchema.js
export class Item {
constructor({
_id = new BSON.ObjectId(),
isComplete = false,
owner_id,
priority = Priority.High, // Default to High priority if none is specified
}) {
this.isComplete = isComplete;
this._id = _id;
this.owner_id = owner_id
this.priority = priority
}
static schema = {
name: 'Item',
properties: {
_id: 'objectId',
isComplete: {type: 'bool', default: false},
summary: 'string',
owner_id: 'string',
priority: {
// Store the index value of the Priority enum rather than the name
type: 'int',
default: Priority.High
},
},
primaryKey: '_id',
};
}

Note

Why Didn't This Break Sync

At this point, your React Native Item model and its corresponding schema in your Atlas App no longer agree. That's okay!

Adding a property to a Realm object is not a breaking change and therefore does not require a client reset. The template app has Development Mode enabled, so changes to the client Realm object are reflected in the server-side schema. For more information, see Enable or Disable Development Mode and Update Your Schema.

3

Your app's data model now includes a priority for each Item object. Let's update the app UI so that you can choose a priority value when you add a new to-do to the list.

First, we'll install an external library to implement the priority picker component. Run the following in your terminal inside of your project root:

npm install @react-native-picker/picker

If you're building for iOS, make sure to link the associated Cocoapods after you've installed the package:

npx pod-install

Tip

You may need to rebuild your app after installing. To do so, stop the bundler for your project and then run the build command:

Now that the package is fully installed, let's update the new to-do creation prompt component to use the picker.

Add the following imports to the top of src/CreateToDoPrompt.js:

CreateToDoPrompt.js
import {Picker} from '@react-native-picker/picker';
import {Priority} from './ItemSchema';

Then, modify the CreateToDoPrompt component:

  • keep track of priority in a state hook

  • connect the state to the Picker component that you imported

  • pass priority to the onSubmit() handler

src/CreateToDoPrompt.js
export function CreateToDoPrompt(props) {
const {onSubmit} = props;
const [summary, setSummary] = useState(null);
const [priority, setPriority] = useState(Priority.High);
return (
<View style={styles.modalWrapper}>
<Text h4 style={styles.addItemTitle}>
Add Item
</Text>
<Input placeholder="What do you want to do?" onChangeText={setSummary} />
<Picker
style={{width: '80%'}}
selectedValue={priority}
onValueChange={value => setPriority(value)}>
{Priority.map(priority => (
<Picker.Item
key={priority}
label={priority}
value={Priority[priority]}
/>
))}
</Picker>
<Button
title="Save"
buttonStyle={styles.saveButton}
onPress={() => onSubmit({summary, priority})}
/>
</View>
);
}

In src/ItemListView.js, modify the createItem() function to accept and use priority:

ItemListView.js
const createItem = ({summary, priority}) => {
// if the realm exists, create a to-do item
if (realm) {
realm.write(() => {
realm.create('Item', {
_id: new BSON.ObjectID(),
owner_id: user.id,
summary,
priority,
});
});
}
};

Then, modify the new to-do submission handler to accept the priority level and pass it to createItem():

ItemListView.js
<CreateToDoPrompt
onSubmit={({summary, priority}) => {
setShowNewItemOverlay(false);
createItem({summary, priority});
}}
/>

Finally, modify each to-do's list item to include its priority before the summary:

ItemListView.js
<ListItem key={`${item._id}`} bottomDivider topDivider>
<Text>{item.priority}</Text>
<ListItem.Title style={styles.itemTitle}>
{item.summary}
</ListItem.Title>
<ListItem.CheckBox
checked={item.isComplete}
onPress={() => toggleItemIsComplete(item._id)}
/>
<Button
type="clear"
icon={
<Icon
name="times"
size={12}
color="#979797"
onPress={() => deleteItem(item._id)}
/>
}
/>
</ListItem>
4

Your app should now allow users to set a priority level for each new to-do item.

Rebuild the app and open it. Add some new to-do items to confirm that you can choose a priority level and that the list displays each to-do's priority.

Screenshots of the priority picker and the to-do list showing each to-do's priority.

The Device Sync protocol uses a flexible model where each sync client uses a standard RQL query to choose a subset of application data and then subscribes to the subset. This automatically pulls the latest version of all data in the subset to the device and syncs changes to the data between devices.

For example, the template app you're using has the following built-in subscription to items that the current user owns:

ItemListView.js
await realm.subscriptions.update(mutableSubs => {
// subscribe to all of the logged in user's to-do items
let ownItems = realm
.objects("Item")
.filtered(`owner_id == "${user.id}"`);
// use the same name as the initial subscription to update it
mutableSubs.add(ownItems, {name: "ownItems"});
});

You can customize the subscription on the fly to sync only the data that your app needs. Let's add a feature to demonstrate how.

For this tutorial, we'll add a button that lets us toggle between two "modes": one where the app syncs all to-do items and another where it only syncs "important" ones with a priority of High or Severe.

1

First, add a useState() hook to the ItemListView component to keep track of the current mode:

ItemListView.js
const [showImportantOnly, setShowImportantOnly] = useState(false);

Then, add a new button that toggles the mode to the bottom of the to-do list:

ItemListView.js
{items.map(item => (
<ListItem key={`${item._id}`} bottomDivider topDivider>
{/* ... */}
</ListItem>
))}
<Button
title={showImportantOnly ? 'Show All' : 'Show Important Only'}
buttonStyle={{
...styles.addToDoButton,
backgroundColor: showImportantOnly ? '#00A35C' : '#FFC010',
}}
onPress={() => setShowImportantOnly(showImportantOnly => !showImportantOnly)}
/>
2

At this point, the app can switch modes in the UI, but we haven't done anything else so the modes are functionally identical. Let's update the sync subscription to only sync data relevant to the current mode.

In the first useEffect of the ItemListView component, add code that checks the current mode and appends an additional priority filter to the query if "important only" mode is active:

ItemListView.js
useEffect(() => {
// initialize the subscriptions
const updateSubscriptions = async () => {
await realm.subscriptions.update(mutableSubs => {
// subscribe to all of the logged in user's to-do items
let ownItems = realm
.objects("Item")
.filtered(`owner_id == "${user.id}"`);
if(showImportantOnly) {
// 'Severe' or 'High' priority items
ownItems = ownItems.filtered(`priority <= 1`);
}
// use the same name as the initial subscription to update it
mutableSubs.add(ownItems, {name: 'ownItems'});
});
};
updateSubscriptions();
}, [realm, user, showImportantOnly]);

Important

Don't forget to add showImportantOnly to the list of dependencies in the second argument of useEffect.

3

Your app is now set up to modify its sync subscription based on the current mode. However, if you run the app and switch to "important only" mode, you'll see that the list doesn't change and a warning appears in the console:

Possible Unhandled Promise Rejection (id: 0):
Error: Client provided query with bad syntax:
unsupported query for table "Item": key "priority" is not a queryable field
Screenshot of a console warning in an iOS simulator
4

We're getting a warning because a sync client can only filter a subscription based on a given field if that field is specifically listed as a "queryable field" in the Device Sync configuration of your Atlas App.

To get everything working, we need to add priority as a queryable field in the Atlas App's sync configuration.

First, find the template app's backend directory and modify sync/config.json:

{
...,
"queryable_fields_names": [
"_id",
"owner_id",
"priority"
]
}

Then, run the following terminal command from within the backend directory to deploy the updated sync configuration:

npx mongodb-realm-cli push
5

Once your updated sync configuration is deployed, your app is complete and fully functional. Congratulations!

Rebuild and run the app to make sure everything works. You should be able to create, complete, and delete to-do items as well as switch to and from "important only" mode.

Screenshots of the finished app in both "show all" and "important only" mode

Note

Give Feedback

How did it go? Use the Give Feedback tab at the bottom right of the page to let us know if this tutorial was helpful or if you had any issues.

←  Xamarin (Android & iOS) TutorialFlutter Tutorial →
Give Feedback
© 2022 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2022 MongoDB, Inc.