Mobile Bytes #9 : Realm React for React Native

Greetings Realm folks,

My name is Andrew Meyer, one of the engineers at realm-js, and I am making a guest post today in @henna.s Realm Byte column to help React Native users get started with our new library @realm/react. :smiley:

@realm/react is a module built on top of realm-js with the specific purpose of making it easier to implement Realm in React.

I wanted to provide a quick example for React Native developers, to get an idea of how easy it is to get started using Realm using @realm/react. Therefore, I made an 80 line example of how to create a simple task manager using the library.

You only need to have @realm/react and realm installed in your project and you will be good to go. If you aren’t using TypeScript, simply modify the Task class to not use types.

Here is a breakdown of the code.

Model Setup

Setting up and thinking about your model is the first step in getting any application off the ground. For our simple app, we are defining a Task model with a description, completion flag, and creation timestamp. It also contains a unique _id, which is the primary key of the Task model. It’s good to define a primary key, in case you want to reference a single Task in your code later on.

We have also added a generate method. This is a convenience function that we will use to create new tasks. It automatically generates a unique _id, sets the creation timestamp, and sets the description provided by its argument.

The schema property is also required for Realm. This defines the structure of the model and tells Realm what to do with the data. Follow Realm Object Model for more information.

Here is the code for setting up your model class:

class Task extends Realm.Object {
  _id!: Realm.BSON.ObjectId;
  description!: string;
  isComplete!: boolean;
  createdAt!: Date;

  static generate(description: string) {
    return {
      _id: new Realm.BSON.ObjectId(),
      description,
      createdAt: new Date(),
     };
  }

  static schema = {
    name: 'Task',
    primaryKey: '_id',
    properties: {
      _id: 'objectId',
      description: 'string',
      isComplete: { type: 'bool', default: false },
      createdAt: 'date'
    },
  };
}

Setting up the RealmProvider

The next part of the code is a necessary part in setting up your application to interact with Realm using hooks. In this code, we are calling createRealmContext which will return an object containing a RealmProvider and a set of hooks (useRealm, useQuery and useObject).

The RealmProvider must wrap your application in order to make use of the hooks. When the RealmProvider is rendered, it will use the configuration provided to the createRealmContext to open the Realm when it is rendered. Alternatively, you can set the configuration through props on RealmProvider.

Here is the code for setting up your application wrapper:

const { RealmProvider, useRealm, useQuery } = createRealmContext({ schema: [Task] })

export default function AppWrapper() {
  return (
    <RealmProvider><TaskApp /></RealmProvider>
  )
}

Application Component

Now that you have an idea of how to set everything up, let’s move on to the application. You can see right away that two of the hooks we generated are being used. useRealm is being used to perform any write operations, and useQuery is used to access all the Tasks that have been created.

The application is providing a TextInput that will be used to generate a new Task. Once a Task is created, it will be displayed in the FlatList below. That timestamp we set up earlier is used to keep the list sorted so that the newest task is always at the top.

In order to keep this code short, we skipped a few best practices. All the methods provided to the application should ideally be set to variables and wrapped in a useCallback hook, so that they are not redefinined on every re-render. We are also using inline styles to spare a few more lines of code. One would normally generate a stylesheet using Stylesheet.create.

Here is the code for the application component:

function TaskApp() {
  const realm = useRealm();
  const tasks = useQuery(Task);
  const [newDescription, setNewDescription] = useState("")

  return (
    <SafeAreaView>
      <View style={{ flexDirection: 'row', justifyContent: 'center', margin: 10 }}>
        <TextInput
          value={newDescription}
          placeholder="Enter new task description"
          onChangeText={setNewDescription}
        />
        <Pressable
          onPress={() => {
            realm.write(() => {
              realm.create("Task", Task.generate(newDescription));
            });
            setNewDescription("")
          }}><Text>➕</Text></Pressable>
      </View>
      <FlatList data={tasks.sorted("createdAt")} keyExtractor={(item) => item._id.toHexString()} renderItem={({ item }) => {
        return (
          <View style={{ flexDirection: 'row', justifyContent: 'center', margin: 10 }}>
            <Pressable
              onPress={() =>
                realm.write(() => {
                  item.isComplete = !item.isComplete
                })
 }><Text>{item.isComplete ? "✅" : "☑️"}</Text></Pressable>
            <Text style={{ paddingHorizontal: 10 }} >{item.description}</Text>
            <Pressable
              onPress={() => {
                realm.write(() => {
                  realm.delete(item)
                })
              }} ><Text>{"🗑️"}</Text></Pressable>
          </View>
        );
      }} ></FlatList>
    </SafeAreaView >
  );
}

Todo list in 80 lines of code

Here is the example in full, including all the required import statements.

import React, { useState } from "react";
import { SafeAreaView, View, Text, TextInput, FlatList, Pressable } from "react-native";
import { Realm, createRealmContext } from '@realm/react'
class Task extends Realm.Object {
  _id!: Realm.BSON.ObjectId;
  description!: string;
  isComplete!: boolean;
  createdAt!: Date;

  static generate(description: string) {
    return {
      _id: new Realm.BSON.ObjectId(),
      description,
      createdAt: new Date(),
    };
  }

  static schema = {
    name: 'Task',
    primaryKey: '_id',
    properties: {
      _id: 'objectId',

description: 'string',
      isComplete: { type: 'bool', default: false },
      createdAt: 'date'
    },
  };
}

const { RealmProvider, useRealm, useQuery } = createRealmContext({ schema: [Task] })

export default function AppWrapper() {
  return (
    <RealmProvider><TaskApp /></RealmProvider>
  )
}

function TaskApp() {
  const realm = useRealm();
  const tasks = useQuery(Task);
  const [newDescription, setNewDescription] = useState("")

  return (
    <SafeAreaView>
      <View style={{ flexDirection: 'row', justifyContent: 'center', margin: 10 }}>
        <TextInput
          value={newDescription}
          placeholder="Enter new task description"
          onChangeText={setNewDescription}
        />
        <Pressable
          onPress={() => {
            realm.write(() => {
              realm.create("Task", Task.generate(newDescription));
            });
            setNewDescription("")
          }}><Text>➕</Text></Pressable>
      </View>
      <FlatList data={tasks.sorted("createdAt")} keyExtractor={(item) => item._id.toHexString()} renderItem={({ item }) => {
        return (
          <View style={{ flexDirection: 'row', justifyContent: 'center', margin: 10 }}>
            <Pressable
onPress={() =>
                realm.write(() => {
                  item.isComplete = !item.isComplete
                })
              }><Text>{item.isComplete ? "✅" : "☑️"}</Text></Pressable>
            <Text style={{ paddingHorizontal: 10 }} >{item.description}</Text>
            <Pressable
              onPress={() => {
                realm.write(() => {
                  realm.delete(item)
                })
              }} ><Text>{"🗑️"}</Text></Pressable>
          </View>
        );
      }} ></FlatList>
    </SafeAreaView >
  );
}

Tell me more

For more details on how to use @realm/react checkout our README and our documentation. If you are just getting started with React Native, you can also use our Expo templates to get started with minimal effort.

Let us know what you think

And with that being said, what do you think about @realm/react? Any other examples you would like to see? We are working hard to make it easy to integrate realm-js with react-native, so let us know if you have any questions or feature requests!

Happy Realming!

5 Likes

Wow, great development! I was waiting for a wrapper like this. To test it out it created a new Expo project based on the javascript Expo template you provided (not the TypeScript). The todo app runs flawlessly but once I enable the sync then it throws me a partitionValue must be of type ‘string’, ‘number’, ‘objectId’, or ‘null’ error. I tried several things to resolve it especially using the example code you provided for Native React. I got stuck. Any ideas what I do wrong here?

1 Like

Hi @Joost_Hazelzet, the partitionValue must be setup in Atlas.


If you want to enable Sync, you will need to set a partitionValue. This can be arbitrary, but it is usually an ID that the data can be filtered on (as in this example, the userID).
You can dynamically set the partitionValue in the RealmProvider component:

return (
    <RealmProvider sync={{user: loggedInUserObject, partitionValue: "someValue"}} ><TaskApp /></RealmProvider>
  )

The value should mirror the same type that you have setup in Atlas.
Glad you are enjoying the library! Let us know if this helps :slight_smile:

1 Like

Hey Andrew, yes it was the missing partitionValue in the RealmProvider and and now the Todo app is running like a charm including the tasks synched to the Atlas database. Thank you. :smiley:

My code is available as Github repository https://github.com/JoostHazelzet/expoRealm-js in case somebody wants to use it.

3 Likes

Can we setup multiple partition value in a single object?

Hi Andrew,

The app I’m trying to build currently needs to keep track of ordering for a set of items. My data is structured such that a Group has a Realm.List. I am using the useObject() hook to get the Group, and then I’m trying to render the Group.items in a React Native FlatList. However, I can’t use the Realm.List type as an argument to the data prop of FlatList. I have tried using the useQuery() hook to get that same list of Items, but I need to preserve ordering in the Group, so what I really need is access to the Group so I can add/remove items from Group.items.

Do you know of a way I can render a Realm.List?

Ah nevermind, I was able to get it working with react native’s VirtualizedList instead of using the FlatList which is more restrictive

You can create multiple RealmProviders with createRealmContext and have each using a different partition value. You will just have to export and use the hooks related to said partition.

2 Likes

I have written tests to do exactly what you have described. What exactly is the problem you are experiencing when trying to use a Realm.List in a FlatList? Feel free open an issue on github and provide some more information. I want to make sure that works :slight_smile:

3 Likes

I tried again and it works using a FlatList. I had just incorrectly specified an incorrect type for my parameter in my keyExtractor prop. I had specified the type of the variable as item: Item & Realm.Object instead of just Item. This was just a mistake on my part while I’m getting familiar with using Realm still! Thank you for the help!

2 Likes

Hi @Andrew_Meyer,

This is a great example. Do you have something similar for implementing authentication? The example from Mongo talks uses React and I’m having trouble adapting it. A React Native example would be much appreciated.

I’ll continue to search the forums to see if such an example already exists.

Thanks,
Josh

Thanks! Good information

The missing part in this tutorial is how do you access the Realm instance outside of the components (unless i’m missing something not everything is a component in a React app).
When I tried the useRealm / useQuery / … hooks outside of components, I got the “Hooks can only be called inside of the body of a function component”.
And if I try to create a new Realm() outside, either I get errors because the Realm is already opened with another schema OR the realm instance close randomly (Garbage Collected?).

So I’m really curious to know how we are supposed to handle this.

@Julien_Curro Thanks for reaching out. This is doable. We have added a flag to the RealmProvider in version 0.6.0 called closeOnUnmount which can be set to false to stop the Realm from closing if you are trying to do anything with the Realm instance outside of the component. Without setting this flag, as soon as the RealmProvider goes out of scope, the realm instance instantiated therein will be closed.
It’s important to note, that with realm instances that are instantiated and point to the same realm, when one of them is closed, they all close. We will address this in a future version of the Realm JS SDK, but for now, the closeOnUnmount flag can be used to workaround this.
Another note, any of the hooks will only work within components rendered as children of the RealmProvider. Anything done with a realm instance outside of this provider must be done without hooks. This includes registering your own change listeners if you want to react to changes on data, which the hooks handle automatically.
Let us know if you have any other issues :slight_smile:

I am testing the closeOnUnmount prop, but I don’t know how I am supposed to get the 2nd non-context related realm instance.

Am I supposed to use the realmRef prop ? Or should I just

Before your post I was trying with an ugly singleton like this :

import { Realm } from '@realm/react';
import { realmConfig } from "./schema";

class RealmInstance {
    private static _instance: RealmInstance;
    public realm!: Realm;
    private constructor() {}

    public static getInstance(): RealmInstance {        
        if (!RealmInstance._instance) {
            RealmInstance._instance = new RealmInstance();
            RealmInstance._instance.realm = new Realm(realmConfig);
            console.log('DB PATH:', RealmInstance._instance.realm.path)
        }
        if (RealmInstance._instance.realm.isClosed) {
            RealmInstance._instance.realm = new Realm(realmConfig);
        }

        return RealmInstance._instance;
    }
}

export default RealmInstance.getInstance().realm;

Edit: is there a discord somewhere to talk about Realm ? There’s a mongodb server, but nobody seems to know what Realm is :frowning:

@Julien_Curro The singleton example you posted should work for this purpose. The realmConfig you are using here is spreadable onto <RealmProvider {...realmConfig} closeOnUnmount={false}>. If you open a Realm with the same config, you get a shared instance of the same Realm.
The only change I would suggest is to change new Realm to Realm.open. Realm.open is async and more suited for a Realm configured with sync settings.

At the moment we do not have a discord, but you are not the first to ask about this. We are currently trying to merge Realm even closer the MongoDB, so hopefully in the near future the discord is more knowledgable on these topics.

closeOnUnmount seems to be working and I finally simplified the singleton, since in TS I can export the instance instead of a class, it’s easier like that :

import { Realm } from '@realm/react';
import { realmConfig } from "./schema";

const RealmInstance = new Realm(realmConfig)
export default RealmInstance

Thanks for your time, and would be happy to ask other things to you on any discord you want :smiley:

Hello @Julien_Curro ,

Thank you for raising your questions and thanks @Andrew_Meyer for taking the time to help our fellow member :smiling_face:

@Julien_Curro , please feel free to ask questions and share your solutions in the community forum in related categories so we have a knowledge house of information everyone can benefit from :wink: We as a community appreciate your contributions :handshake:

Happy Coding!

Cheers, :performing_arts:
Henna
Community Manager, MongoDB

Hey @henna.s , @Andrew_Meyer

I am using realm with device sync. Can I set up a SubscriptionProvider instead of doing the subscription directly in the screens?

<RealmProvider 
sync={{
              flexible: true,
              onError: (_, error) => {
                console.log(error);
              },
            }}
>
<SubscriptionProvider>
<TaskApp />
<SubscriptionProvider>
</RealmProvider>

@Siso_Ngqolosi This is allowed and looks like a good setup. Subscriptions are globally defined, so if you apply them in any section of you app it will effect all components using the Realm.
Let us know if you have any issues!

1 Like