Docs Home → Atlas App Services
React Native Tutorial
On this page
- Overview
- Prerequisites
- Start with the Template App
- Set Up the Template App
- Install Dependencies
- Build the App
- Test the App
- Get Familiar With the Template App
- Atlas App Services App
- React Native App
- Add a Priority Level Field
- Define the Priority Levels
- Update the
Item
Data Model - Add a Priority Picker
- Run and Test the App
- Update the Sync Subscription
- Add a Mode Toggle to the UI
- Update the Sync Subscription
- Test the App
- What's Next?
Overview
You can use the Realm React Native SDK and @realm/react 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 TypeScript template application to see how everything fits together.
The app is a pre-built template that includes a working React Native application (frontend) and its corresponding App Services App configuration files (backend).
The template app is a basic to-do list application that lets users do various things to manage their tasks:
Create email/password accounts and log in and out of the app.
Create, read, update, and delete their own tasks.
View all tasks, even if the user is not the owner.
After you've got the template app running, 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 example
illustrates how you might adapt the template app for your own needs.
You would not necessarily make this change given the current structure of
the template app.
The template app provides a toggle that simulates the device being in an offline mode. This toggle lets you quickly test Device Sync functionality, emulating the user having no internet connection. However, you would likely remove this toggle in a production application.
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.
Prerequisites
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.
This tutorial starts with a Template App. You need an Atlas Account, an API key, and
realm-cli
to create a Template App.You can learn more about creating an Atlas account in the Atlas Getting Started documentation. For this tutorial, you need an Atlas account with a free-tier cluster.
You also need an Atlas API key for the MongoDB Cloud account you wish to log in with. You must be a Project Owner to create a Template App using
realm-cli
.To learn more about installing
realm-cli
, see Install realm-cli. After you have installedrealm-cli
, login using the API key for your Atlas project.
Start with the Template App
This tutorial is based on the React Native SDK Flexible Sync Template App named
react-native.todo.flex
. We start with the default app and build new features
on it.
To learn more about the Template Apps, see Template Apps.
If you don't already have an Atlas account, sign-up to deploy a Template App.
Set Up the Template App
Use the following steps to get the template app up and running on your computer:
Install Dependencies
In your terminal, go to the directory that contains the client code. If you
created the app with the App Services CLI, go to
realm-sync-tutorial/frontend/react-native.todo.flex
.
Otherwise, go to the root of your downloaded or cloned project. Then
run following commands to navigate to install the app's dependencies:
npm install
To build and run the app on an iOS device or simulator, install the additional iOS dependencies with CocoaPods.
npx pod-install
Build the App
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.
Test the App
When the build completes, you should have a functional app running on your simulator. In the app, register a new account and test the features:
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.
Toggle internet connectivity in the app to simulate offline mode.
If you connect to your Atlas Cluster and query the
todo.Item
collection you can see your App's data. As long as the React Native
app is not in offline mode, new data and changes in the app automatically
sync to the todo.Item
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 a network connection is available.
Tip
To learn more about updating data in your cluster, see Update Documents.
Get Familiar With the Template App
Now that you have the template app running let's dive into the code to see what we're working with.
Atlas App Services App
The template app includes a fully configured App Services 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 data model for the
todo.Item
collection that matches theItem
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 their own items and view other users' items.
React Native App
The React Native app is a fully configured mobile client that can run on iOS and Android devices.
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.tsx | The Item class, including its object data model. We import this
class in RealmContext.ts to include it in the app's overall Realm
schema. |
RealmContext.ts | The Realm hooks and providers that we create and export to work
with Realm throughout the app. |
App.tsx | The root component of the app, which we wrap with the Realm hook
providers from RealmContext.ts . |
WelcomeView.tsx | The user registration and login form that users see when they
first open the app. |
ItemListView.tsx | The main to-do list app that users interact with after they log
in. It queries for Item Realm objects and displays them
in a list. It also includes the code to create new Item
objects and store them in Realm. |
CreateToDoPrompt.tsx | A UI form that lets us enter data for new Item objects. The
code that actually creates new objects is in ItemListView.tsx . |
LogoutButton.tsx | A reusable button that logs out an authenticated user. |
OfflineModeButton.tsx | A reusable button that simulates an offline mode by pausing and resuming
the current Realm syncSession . |
Add a Priority Level Field
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 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.
Define the Priority Levels
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.tsx
:
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 (meaning more important)
has a lower index value.
Update the Item
Data Model
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.tsx
to
add priority
to the Item
data model:
export class Item extends Realm.Object<Item> { _id!: BSON.ObjectId; isComplete!: boolean; summary!: string; owner_id!: string; priority!: string; static schema: Realm.ObjectSchema = { name: 'Item', primaryKey: '_id', properties: { // This allows us to automatically generate a unique _id for each Item _id: {type: 'objectId', default: () => new BSON.ObjectId()}, // All todo items will default to incomplete 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 }, }, }; }
Note
Why Didn't This Break Sync
At this point, your React Native Item
model and its
corresponding schema in your App Services 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 Development Mode and Update Your Data Model.
Add a Priority Picker
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.tsx
:
import {Picker} from '@react-native-picker/picker'; import {Priority} from './ItemSchema';
Then, modify the CreateToDoPrompt
component:
Add
priority
to theonSubmit()
props definitionKeep track of
priority
in a state hookConnect the state to the
Picker
component that you importedPass
priority
to theonSubmit()
handler
type Props = { onSubmit(args: {summary: string; priority: string;}): void; }; export function CreateToDoPrompt(props: Props): React.ReactElement<Props> { const {onSubmit} = props; const [summary, setSummary] = useState(''); const [priority, setPriority] = useState(Priority.High); return ( <View style={styles.modalWrapper}> <Text h4 style={styles.addItemTitle}> Add To-Do Item </Text> <Input placeholder="What do you want to do?" onChangeText={(text: string) => setSummary(text)} autoCompleteType={undefined} /> <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.tsx
, modify the createItem()
function
to accept and use priority
:
const createItem = useCallback( ({summary, priority}: {summary: string, priority: string}) => { realm.write(() => { return new Item(realm, { summary, owner_id: user?.id, priority }); }); }, [realm, user], );
Then, modify the create to-do submission handler to accept the
priority
level and pass it to createItem()
:
<CreateToDoPrompt onSubmit={({summary, priority}) => { setShowNewItemOverlay(false); createItem({summary, priority}); }} />
Finally, modify the list item template to render the to-do's priority
before the summary
:
<ListItem key={`${item._id}`} bottomDivider topDivider hasTVPreferredFocus={undefined} tvParallaxProperties={undefined}> <Text>{item.priority}</Text> <ListItem.Title style={styles.itemTitle}> {item.summary} </ListItem.Title> <ListItem.Subtitle style={styles.itemSubtitle}> {item.owner_id === user?.id ? '(mine)' : ''} </ListItem.Subtitle> <ListItem.CheckBox checked={item.isComplete} checkedColor={COLORS.primary} iconType="material" checkedIcon="check-box" uncheckedIcon="check-box-outline-blank" onPress={() => toggleItemIsComplete(item._id)} /> <Button type="clear" onPress={() => deleteItem(item._id)} icon={ <Icon type="material" name="clear" size={12} color="#979797" tvParallaxProperties={undefined} /> } /> </ListItem>
Update the Sync Subscription
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:
realm.subscriptions.update(mutableSubs => { mutableSubs.removeByName(itemSubscriptionName); mutableSubs.add( realm.objects(Item).filtered(`owner_id == "${user?.id}"`), {name: ownItemsSubscriptionName}, ); });
You can customize the subscription during runtime 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.
Add a Mode Toggle to the UI
First, add a useState()
hook to the ItemListView
component
to keep track of the current mode:
const [showImportantOnly, setShowImportantOnly] = useState(false);
Then, add a new button that toggles the mode to the bottom of the
to-do list, after <ListItem>
:
<Button title={showImportantOnly ? 'Show All' : 'Show Important Only'} buttonStyle={{ ...styles.addToDoButton, backgroundColor: showImportantOnly ? '#00A35C' : '#FFC010', }} onPress={() => setShowImportantOnly(showImportantOnly => !showImportantOnly)} />
Update the Sync Subscription
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 the showImportantOnly
mode is
active:
useEffect(() => { if (showAllItems) { realm.subscriptions.update(mutableSubs => { mutableSubs.removeByName(ownItemsSubscriptionName); mutableSubs.add(realm.objects(Item), {name: itemSubscriptionName}); }); } else if (showImportantOnly) { realm.subscriptions.update(mutableSubs => { mutableSubs.removeByName(itemSubscriptionName); mutableSubs.add( realm.objects(Item).filtered(`owner_id == "${user?.id}" && priority <= 1`), {name: ownItemsSubscriptionName}, ); }); } else { realm.subscriptions.update(mutableSubs => { mutableSubs.removeByName(itemSubscriptionName); mutableSubs.add( realm.objects(Item).filtered(`owner_id == "${user?.id}"`), {name: ownItemsSubscriptionName}, ); }); } }, [realm, user, showAllItems, showImportantOnly]);
Important
Don't forget to add showImportantOnly
to the list of
dependencies in the second argument of useEffect
.
What's Next?
Read our React Native SDK documentation.
Find developer-oriented blog posts and integration tutorials on the MongoDB Developer Hub.
Join the MongoDB Community forum to learn from other MongoDB developers and technical experts.
Add a feature to the template app by using Triggers and Atlas Search.
Explore engineering and expert-provided example projects.
Note
Share Feedback
How did it go? Use the Share Feedback tab at the bottom right of the page to let us know if this tutorial was helpful or if you had any issues.