Docs Menu

React Native Tutorial

On this page

MongoDB Realm provides a Node SDK that allows you to create a Mobile Application with React Native. This tutorial illustrates the creation of a "Task Tracker" React Native application that allows users to:

  • Sign in to their account with their email and password and sign out later.
  • View a list of projects they are a member of.
  • View, create, modify, and delete tasks in projects.
  • View a list of team members in their project.
  • Add and remove team members to their project.

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 MongoDB Realm application.

Ensure that you have the following:

  • nodejs version 10.x or later.
  • If you plan to run the app on an iOS device or emulator: Xcode version 11.0 or higher, which requires macOS 10.14.4 or higher.
  • If you plan to run the app on an Android device or emulator: Android Studio.
  • The React Native CLI. Detailed instructions for setting up your development environment, including the CLI tools, can be found in the React Native docs.
  • Set up the backend Realm app.
1

We've put together a React Native application that has most of the code you'll need. Clone the client application repository directly from GitHub:

git clone --branch start https://github.com/mongodb-university/realm-tutorial-react-native.git
Tip

The start branch is an incomplete version of the app that we will complete in this tutorial. To view the finished app, check out the final branch, install dependencies (as described in the next step), and update getRealmApp.js with your Realm app ID.

2

In your terminal, run the following commands to navigate to the task tracker client application and install its dependencies:

cd realm-tutorial-react-native
npm install

If you plan to run the app on an iOS device or emulator, you need to install additional dependencies with CocoaPods. Change directories to the ios directory and run pod install, as shown here:

cd ios
pod install --repo-update
cd ..

Be sure to switch back to the realm-tutorial-react-native root directory after installing the pods.

3

At this point, you have a React Native app that can run on both iOS and Android. We haven't yet implemented the necessary code for app functionality, so there will be errors when the app loads, but you can ensure that the app deploys properly.

Start by running Metro in a terminal window by using the following command:

npx react-native start

Leave Metro running, and in a separate terminal window, issue one of the following commands, depending on whether you want to run on iOS or Android:

npx react-native run-ios

or

npx react-native run-android

After the emulator is started and the bits have been deployed, you should see the app is "running" and one or more errors are displayed:

The initial state of React Native app
Important
CompileC Error

If, when compiling the iOS app, you get a CompileC build error that involves "Flipper," you will need to edit /ios/Podfile. Look for the section that enables Flipper and comment out those lines:

# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
# use_flipper!
#post_install do |installer|
# flipper_post_install(installer)
#end

For a better understanding of the files used in this tutorial, the following table highlights some of the more important files. :

File
Purpose
getRealmApp.js
Provides access to the Realm app instance.
App.js
Defines which screens are in the app and sets up the navigation system.
schemas.js
Defines the Realm object models used in this app.
providers/AuthProvider.js
Defines the AuthProvider, which handles user login, log out, and project membership information for the current user.
providers/TasksProvider.js
Defines the TasksProvider, which handles fetching, adding, updating, and deleting tasks within a project.
components/ActionSheet.js
A component that presents the user with a list of actions.
components/AddTask.js
A button that, when pressed, prompts the user to enter new task information and creates it with the given createTask function.
components/Logout.js
A button that handles user log out.
components/ManageTeam.js
A list of team members on the current user's project that provides controls for adding and removing team members.
components/TaskItem.js
A list item that represents a task in a list. When pressed, it presents an ActionSheet with actions corresponding to deleting the task or updating the task's status.
views/ProjectsView.js
The screen that presents a list of projects that the current user is a team member of.
views/TasksView.js
The screen showing the tasks for a given project. If this is the logged-in user's own project, the TasksView includes a button open a ManageTeam view.
views/WelcomeView.js
The screen that allows a user to log in or register a user.
1

To get the app working with your backend, you first need to instantiate the Realm app. The Realm app is the interface to the MongoDB Realm backend. Navigate to the getRealmApp.js file and complete the getRealmApp() function implementation:

// Returns the shared instance of the Realm app.
export function getRealmApp() {
if (app === undefined) {
const appId = "<your Realm app ID here>"; // Set Realm app ID here.
const appConfig = {
id: appId,
timeout: 10000,
app: {
name: "default",
version: "0",
},
};
app = new Realm.App(appConfig);
}
return app;
}

Be sure to enter your own Realm app ID, which you can find in the Realm UI.

2

Navigate to providers/AuthProvider.js. In this file, and in the providers/TasksProvider.js file that we will complete a bit later, we use React Context and hooks to implement a data provider pattern. Don't worry if you aren't too familiar with these constructs yet. The basic idea is as follows:

  • The Provider component handles fetching and modifying data.
  • Any descendant of the Provider component can use a hook function to access the provider's data as well as its commands for modifying the data.

The AuthProvider is responsible for logging in, registering a user, logging out, and retrieving the list of projects that a user is a member of from the custom user data object.

First, let's implement the login functionality that a descendant component can pass an email and password in order to log in:

// The signIn function takes an email and password and uses the
// emailPassword authentication provider to log in.
const signIn = async (email, password) => {
const creds = Realm.Credentials.emailPassword(email, password);
const newUser = await app.logIn(creds);
setUser(newUser);
};

Next, implement the user registration functionality by using the email/password provider of the Realm app:

// The signUp function takes an email and password and uses the
// emailPassword authentication provider to register the user.
const signUp = async (email, password) => {
await app.emailPasswordAuth.registerUser(email, password);
};

Next, implement the log out functionality:

// The signOut function calls the logOut function on the currently
// logged in user
const signOut = () => {
if (user == null) {
console.warn("Not logged in, can't log out!");
return;
}
user.logOut();
setUser(null);
};
3

The AuthProvider must access the custom user data object so it can provide a list of projects associated with the user. We have set up our app so that the custom user data object exists in a specific realm, where only one user object exists.

Note
How Do We Know Which Projects a User Can Access?

The backend you imported makes exactly one custom user data object for each user upon signup. This custom user data object contains a list of partitions a user can read and a list of partitions a user can write to.

The backend is set up so that every user has read-only access to their own custom user data object. The backend also has functions to add and remove access to projects, which we will use later when we add the Manage Team view.

By managing the custom user data object entirely on the backend and only providing read-only access on the client side, we prevent a malicious client from granting themselves arbitrary permissions.

To access the custom user data, we will create a Configuration object that specifies a partition value of the pattern user=<USER_ID> and then add code to open the realm.

Because there may be a delay between when a user first registers and when the authentication trigger creates the user custom data object, the realm may be empty when first opened. Rather than have an empty realm, we need to add an observer to the collection of users in that realm which watches for the appearance of the user object and any changes to its project memberships. When a user object is available, your code will read its memberOf field to populate the available project data for any descendant of the provider:

const config = {
sync: {
user,
partitionValue: `user=${user.id}`,
},
};
// Open a realm with the logged in user's partition value in order
// to get the projects that the logged in user is a member of
Realm.open(config).then((userRealm) => {
realmRef.current = userRealm;
const users = userRealm.objects("User");
users.addListener(() => {
// The user custom data object may not have been loaded on
// the server side yet when a user is first registered.
if (users.length === 0) {
setProjectData([myProject]);
} else {
const { memberOf } = users[0];
setProjectData([...memberOf]);
}
});
});

Finally, the Realm SDK requires you to close any realm you open when you are finished with it. You can return a cleanup function from the effect, which will close the realm any time the user changes or the component is unmounted:

return () => {
// cleanup function
const userRealm = realmRef.current;
if (userRealm) {
userRealm.close();
realmRef.current = null;
setProjectData([]); // set project data to an empty array (this prevents the array from staying in state on logout)
}
};
Note

Check out the App.js, view/WelcomeView.js, and view/ProjectView.js files to see how they use the AuthProvider's signIn, signUp, and signOut functions and the user data via the useAuth() hook function. The ProjectView also uses the project data to populate the list of projects available to the logged-in user.

You should now be able to run the app, sign in, view your projects, and sign out. We will implement the task management functionality next.

4

In order to work with task objects in the Realm Database, we need to define their schema. Navigate to the schemas.js file to complete the task class's schema definition where it says TODO:

static schema = {
name: "Task",
properties: {
_id: "objectId",
name: "string",
status: "string",
},
primaryKey: "_id",
};
5

The TasksProvider component, like the AuthProvider, manages the data that its descendants can use. As the name implies, the TasksProvider provides the task objects for a given project. The project partition is passed in as a prop.

The first thing to implement is opening the realm for the specified project. When configuring the user realm, we used a partition key value pattern of user=<USER_ID>. Similarly, projects use a partition key value pattern of project=<USER_ID>. For the sake of this tutorial's simplicity, each user always has exactly one project, specified by the partition key value containing their own user ID.

In the effect block of providers/TasksProvider.js, configure and open the realm. Once opened, attach an observer that will update the tasks list when any changes come in:

const config = {
sync: {
user: user,
partitionValue: projectPartition,
},
};
// open a realm for this particular project
Realm.open(config).then((projectRealm) => {
realmRef.current = projectRealm;
const syncTasks = projectRealm.objects("Task");
let sortedTasks = syncTasks.sorted("name");
setTasks([...sortedTasks]);
sortedTasks.addListener(() => {
setTasks([...sortedTasks]);
});
});

Again, Realm requires you to close any realm you open once you are done with it. We can do that by returning a cleanup function from the effect:

return () => {
// cleanup function
const projectRealm = realmRef.current;
if (projectRealm) {
projectRealm.close();
realmRef.current = null;
setTasks([]);
}
};

A user can create, update, and delete tasks in a project that they are a member of. To implement the task creation functionality, find the createTask function and add the following code:

const createTask = (newTaskName) => {
const projectRealm = realmRef.current;
projectRealm.write(() => {
// Create a new task in the same partition -- that is, in the same project.
projectRealm.create(
"Task",
new Task({
name: newTaskName || "New Task",
partition: projectPartition,
})
);
});
};

For this application, we limit updates to changing the status of the task. Find the setTaskStatus function and implement the following code, which changes the Task's status:

const setTaskStatus = (task, status) => {
// One advantage of centralizing the realm functionality in this provider is
// that we can check to make sure a valid status was passed in here.
if (
![
Task.STATUS_OPEN,
Task.STATUS_IN_PROGRESS,
Task.STATUS_COMPLETE,
].includes(status)
) {
throw new Error(`Invalid status: ${status}`);
}
const projectRealm = realmRef.current;
projectRealm.write(() => {
task.status = status;
});
};

Finally, in the deleteTask function, implement the task deletion functionality:

// Define the function for deleting a task.
const deleteTask = (task) => {
const projectRealm = realmRef.current;
projectRealm.write(() => {
projectRealm.delete(task);
setTasks([...projectRealm.objects("Task").sorted("name")]);
});
};

Check out the views/TasksView.js and components/TaskItem.js files to see how they use the provider's task data and functionality via the useTasks() hook function.

6

A user can add other users to their team, which allows them to view, edit, and delete tasks on the user's project. The ManageTeam component implements this functionality by calling a few Realm functions on the backend that we defined earlier.

Navigate to components/ManageTeam.js and find the getTeam function. In this function, we need to fetch the list of current team members:

// getTeam calls the backend function getMyTeamMembers to retrieve the
// team members of the logged in user's project
const getTeam = async () => {
try {
const teamMembers = await user.functions.getMyTeamMembers([]);
setTeamMemberList(teamMembers);
} catch (err) {
Alert.alert("An error occurred while getting team members", err);
}
};

Next, in the addTeamMember function, we will call the backend function addTeamMember to implement add team member functionality:

// addTeamMember calls the backend function addTeamMember to add a
// team member to the logged in user's project
const addTeamMember = async () => {
try {
await user.functions.addTeamMember(newTeamMember);
getTeam();
} catch (err) {
Alert.alert("An error occurred while adding a team member", err.message);
}
};

Finally, in the removeTeamMember function, we call another backend function, named removeTeamMember, that removes a team member:

// removeTeamMember calls the backend function removeTeamMember to remove a
// team member from the logged in user's project
const removeTeamMember = async (email) => {
try {
await user.functions.removeTeamMember(email);
getTeam();
} catch (err) {
Alert.alert("An error occurred while removing a team member", err);
}
};
7

Congratulations! Now that you have completed the code, you can run the app and check functionality. Here are some things you can try in the app:

  • Create a user with email first@example.com
  • Explore the app, then log out or launch a second instance of the app on another device or simulator
  • Create another user with email second@example.com
  • Navigate to second@example.com's project
  • Add, update, and remove some tasks
  • Click "Manage Team"
  • Add first@example.com to your team
  • Log out and log in as first@example.com
  • See two projects in the projects list
  • Navigate to second@example.com's project
  • Collaborate by adding, updating, and removing some new tasks
Tip

If something isn't working for you, you can check out the final branch of this repo to compare your code with our finished solution.

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.

Give Feedback
© 2021 MongoDB, Inc.

About

  • Careers
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.