How to Integrate MongoDB Realm with React: Part 2
Tags: javascript, mongodb, react, webdev
Published: 2021-05-29 20:21:29

_This article was originally published on [my personal blog](https://blog.shahednasser.com/how-to-integrate-mongodb-realm-with-react-part-2/)._

_Please participate in [this survey](https://yiro6gzu369.typeform.com/to/M2TFz504) to voice your opinion as a developer for an upcoming article!_

In the first part of this tutorial, we went over how to set up a MongoDB Realm app with sample data, generate the schema, create and restrict roles, and then integrated it with a React app, implementing an authentication system.

In this tutorial, we'll go over how to ensure only logged-in users by email and password can add reviews, and we'll test adding reviews by users that are not logged in to see MongoDB Realm Roles and data access rules in action.

You can find the code for this tutorial [here](https://github.com/shahednasser/mongodb-realm-tutorial).

---

### Add Reviews Form

We'll start with the add reviews form. This form will be accessed through a link in the card of the restaurant that's showing on the home page. The restaurant ID will be passed as a URL parameter, then whatever review the user enters will be saved to that restaurant. At first, we'll allow all users to access the page to test the difference between the logged-in user and anonymous user, then we'll restrict access to the page to logged-in users only.

Create the component `src/pages/AddReview.js` with the following content:

```js
function AddReview() {
    
}

export default AddReview
```

Just like in the Authentication form, we'll use `yup` for validation and `formik` to make creating a form easier:

```js
const reviewSchema = yup.object().shape({
    review: yup.number().required()
})

function AddReview() {
    const [loading, setLoading] = useState(false)
    
    function submitHandler (values) {
     	//TODO add review   
    }
    
    return (
        <Formik
            initialValues={{
                review: 0
            }}

            validationSchema={reviewSchema}
            onSubmit={submitHandler}>
            
            {({errors, touched, handleSubmit, values, handleChange}) => (
                <Form noValidate onSubmit={handleSubmit}>
                    {loading && <Loading />}
                    {!loading && (<div>
                        <h1>Submit Review</h1>
                        <Form.Row>
                            <Form.Label>Review Score</Form.Label>
                            <Form.Control type="number" name="review" value={values.review} onChange={handleChange} 
                            isValid={touched.review && !errors.review} />
                            <Form.Control.Feedback>{errors.review}</Form.Control.Feedback>
                        </Form.Row>
                        <div className="text-center mt-2">
                            <Button variant="primary" type="submit">Submit</Button>
                        </div>
                    </div>)}
                </Form>
            )}

        </Formik>
    )
}
```

We're just creating a form that has one number input for the review and for validation we're using the `reviewSchema` which just checks that the review is filled and is a number.

Next, we need to add the logic of adding the review to the restaurant by the logged-in user. To do this, first, we need to pass the `mongoContext` prop to the component which has the MongoDB `client` and the Realm `app` instances:

```js
function AddReview({mongoContext: {client, app}}) {
	//...
}
```

Next, we'll get the `id` of the restaurant from the URL param using [useParam](https://reactrouter.com/web/api/Hooks/useparams):

```js
const { id } = useParams()
```

And we'll get the `history` instance to use later to redirect back to home page:

```js
const history = useHistory()
```

We can now add the logic to update the `restaurant` document of the passed `id`, adding the user's `grade`. To do that, we'll first get the `restaurants` collection from our `sample_restaurants` database:

```js
function submitHandler(values){
    const rests = client.db('sample_restaurants').collection('restaurants')
}
```

Next, we'll use the method [updateOne](https://docs.mongodb.com/realm/mongodb/update-documents/#update-a-single-document) which takes a query to choose which document to update, then takes the changes. For us, the query will be the restaurant having the id that is passed as a URL param, and the change will be pushing a new entry into the `grades` array inside the `restaurant` document:

```js
rests.updateOne({"_id": BSON.ObjectID(id)}, {"$push": {"grades": {
      date: new Date(),
      score: values.review,
      user_id: BSON.ObjectID(app.currentUser.id)
}}}).then (() => history.push('/'))
    .catch ((err) => {
    	alert(err)
    	setLoading(false)
    })
```

Notice that:

1. To query the `_id` field, we need to use `BSON.ObjectID` to correctly pass the object id. Make sure to add at the beginning of the file `import { BSON } from 'realm-web'`.
2. the `grades` array holds objects that have `date`,`score`, and `user_id`. This way, we're linking the grade to the appropriate user.
3. `updateOne` returns a promise, so once it resolves we're redirecting to the home page using `history.push('/')`.

And with that, our `AddReview` component is ready. Next, we need to add the new page in our routes in `src/App.js`:

```js
return (
    <Router>
      <Navigation user={user} />
      <MongoContext.Provider value={{app, client, user, setClient, setUser, setApp}}>
        <Container>
          <Switch>
            <Route path="/signup" render={() => renderComponent(Authentication, {type: 'create'})} />
            <Route path="/signin" render={() => renderComponent(Authentication)} />
            <Route path="/logout" render={() => renderComponent(LogOut)} />
            <Route path="/review/:id" render={() => renderComponent(AddReview)} />
            <Route path="/" render={() => renderComponent(Home)} />
          </Switch>
        </Container>
      </MongoContext.Provider>
    </Router>
  );
```

Then, we'll need to add the link to the page inside each restaurant card. To do that, edit the 
 `src/components/RestaurantCard.js` component's return statement:

```js
return (
        <Card className="m-3">
            <Card.Body>
                <Card.Title>{restaurant.name} <Badge variant="warning">{avg}</Badge></Card.Title>
                <Link to={`/review/${restaurant._id}`} className="card-link">Add Review</Link>
            </Card.Body>
        </Card>
    )
```

Notice that we're passing the restaurant id to the link as a parameter.

Let's run the server now:

```bash
npm start
```

Make sure to log in if you aren't already. We'll test how this will work as a guest in a bit.

You should be able to see new links for each restaurant on the home page now.

![](https://res-3.cloudinary.com/hbqmf3mbz/image/upload/q_auto/v1/ghost-blog-images/Screen-Shot-2021-05-29-at-7.28.50-PM.png)

Click on "Add Review" for any of the restaurants. You'll see a number input field, enter any number and click "Submit". If you're logged in, you should see a loader, then you'll be redirected to the home page. You can see that the restaurant's review has changed.

![](https://res-4.cloudinary.com/hbqmf3mbz/image/upload/q_auto/v1/ghost-blog-images/React-App.png)

### Test MongoDB Realm Authorization Roles

If you recall from [part 1](https://blog.shahednasser.com/how-to-integrate-mongo-realm-with-react-part-1/#setup-authentication-in-realm-app), we added a new User role. This user role allows users that have an email to insert or update only the `grades` field of a restaurant. For a user to "belong" to the User role they need to have an email, which we declared in the "Apply When" field:

```json
{
  "%%user.data.email": {
    "%exists": true
  }
}
```

So, an anonymous user does not have permission to make any changes to the `grades` field or any field of the `restaurants` collection.

Let's test that. Log out from the current user. You should still be able to see the Add Review link and be able to access the page, as we still haven't added conditions for a user's authentication.

Try to add a review to any restaurant. Since you're not logged in, you'll get an alert with an error and nothing will be added.

![](https://res-2.cloudinary.com/hbqmf3mbz/image/upload/q_auto/v1/ghost-blog-images/Screen-Shot-2021-05-29-at-7.43.09-PM.png)

As you can see the error says "update not permitted". The user does not belong to the "User" role we created, so they're not allowed to add a review.

Let's now hide the link to "Add Review" for anonymous users in `src/components/RestaurantCard.js`:

```js
{!isAnon(user) && <Link to={`/review/${restaurant._id}`} className="card-link">Add Review</Link>}
```

Add `user` to the list of props for `RestaurantCard`:

```js
function RestaurantCard ({restaurant, user}) {
	//...
}
```

Pass the `user` prop to `RestaurantCard` in `src/pages/Home.js`:

```js
<RestaurantCard key={restaurant._id} restaurant={restaurant} user={user} />
```

And let's add a condition in `src/pages/AddReview.js` to redirect to home page if the user is not logged in:

```js
function AddReview({mongoContext: {client, app, user}}) {
    const [loading, setLoading] = useState(false)
    const { id } = useParams()
    const history = useHistory()

    if (isAnon(user)) {
        history.push('/')
    }
    //...
}
```

Now, if you're not logged in you will not be able to see the review and if you try to access the reviews page directly you'll be redirected to the home page.

Let's test another aspect of the role we created. As we said, the role we created allows logged-in users to update the `grades` field. However, they should not be able to edit any other field.

Let's change the parameter for `updateOne` in `AddReview` to change the name instead:

```js
rests.updateOne({"_id": BSON.ObjectID(id)}, {"name": "test"})
```

This is just to easily test this restriction. Now, login and go to the "Add Review" and click Submit. You'll see the same "update not permitted" message as before.

![](https://res-2.cloudinary.com/hbqmf3mbz/image/upload/q_auto/v1/ghost-blog-images/Screen-Shot-2021-05-29-at-7.51.59-PM.png)

This shows how we can easily manage our users, their roles, and data access through MongoDB Realm.

---

### Conclusion

Using MongoDB Realm allows us to easily create serverless apps while also managing data access, roles, and authentication. It's also available to be used on the web (like in this tutorial), on mobile apps, and more. In this tutorial, we covered the basics that you'll probably need in most use cases. If you dive deeper into it, you'll surely find even more features that will be helpful for your serverless apps.

_If you would like to connect and talk more about this article or programming in general, you can find me on my twitter account [@shahednasserr](https://twitter.com/shahednasserr)_