Navigation

React Native Tutorial

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.

Info With Circle IconCreated with Sketch.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 (including Node version 14)
  • If you wish to run on iOS: Xcode version 11.0 or higher, which requires macOS 10.14.4 or higher.
  • If you wish to run on Android: Android Studio.
  • Set up the backend.
1

We've already put together a task tracker React Native application that has most of the code you'll need. You can clone the client application repository directly from GitHub:

git clone https://github.com/mongodb-university/realm-tutorial-react-native.git
Important With Circle IconCreated with Sketch.Important

The realm-tutorial-react-native repository contains two branches: final and start. The final branch is a finished version of the app as it should look after you complete this tutorial. To walk through this tutorial, please check out the start branch:

git checkout start
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 wish to run the app on iOS, you need to install additional dependencies with CocoaPods:

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

Once the dependency installations are complete, you can run the app using one of the following commands, depending on whether you want to run on iOS:

npx react-native run-ios

Or Android (note: you may need to install Android development tools):

npx react-native run-android
3

Open the realm-tutorial-react-native directory in the text editor of your choice and explore the code in project. Be sure to check out the following files:

FilePurpose
getRealmApp.jsProvides access to the Realm app instance.
App.jsDefines which screens are in the app and sets up the navigation system.
schemas.jsDefines the Realm object models used in this app.
providers/AuthProvider.jsDefines the AuthProvider, which handles user login, log out, and project membership information for the current user.
providers/TasksProvider.jsDefines the TasksProvider, which handles fetching, adding, updating, and deleting tasks within a project.
components/ActionSheet.jsA component that presents the user with a list of actions.
components/AddTask.jsA button that, when pressed, prompts the user to enter new task information and creates it with the given createTask function.
components/Logout.jsA button that handles user log out.
components/ManageTeam.jsA list of team members on the current user's project that provides controls for adding and removing team members.
components/TaskItem.jsA 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.jsThe screen that presents a list of projects that the current user is a team member of.
views/TasksView.jsThe 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.jsThe screen that allows a user to log in or register a user.
4

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.

5

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 to 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);
};

Finally, the AuthProvider must access the user custom data object for the logged-in user in order to provide the list of projects that the user is a member of. We have set up our app so that the custom user data object exists in a specific realm, where at most one user object exists.

Info With Circle IconCreated with Sketch.Note

To learn more about how Realm Object models are used in React Native applications, see Realm Objects in our React Native client guide.

Open the realm for that user with the partition key value following the pattern user=<USER_ID>.

There may be a delay between when a user first registers and when the authentication trigger creates the user custom data object. Therefore, the realm may be empty when first opened. Add an observer to the collection of users in that realm in order to watch for the user object first appearing as well as any changes to the its project memberships. When a user object is available, 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)
}
};

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 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. Next, we need to implement the task management functionality.

6

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",
_partition: "string?",
name: "string",
status: "string",
},
primaryKey: "_id",
};
7

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 the opening of the realm for the given project. Whereas the user realm used the partition key value pattern of user=<USER_ID>, projects use the 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, 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. Implement the task creation functionality like so:

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,
})
);
});
};

We limit updates to changing the status of the task. In a write block, you can assign to the task object's members to have it persisted to the database:

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, 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.

8

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. First, 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, call the addTeamMember function on the backend 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, call the removeTeamMember function on the backend to implement remove team member functionality:

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

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
Bulb IconTip

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.

Info With Circle IconCreated with Sketch.Note
Leave Feedback

How did it go? Please let us know if this tutorial was helpful or if you had any issues by using the feedback widget on the bottom right of the page.

Give Feedback