Aman Mittal 👨‍💻 Developer 👉 Node.js, React, React Native | Tech Blogger with 1M+ views on Medium

How to integrate FaunaDB in React apps with Material UI

11 min read 3186

Flutterdb react

There are several ways to create a single page application (SPA) using libraries like React. There are many serverless options to choose from when picking a tech stack for a SPA. One popular option is to integrate FaunaDB into React apps.

FaunaDB is a database as a service (DBaaS) that helps to save time by using a preexisting infrastructure to build web applications without setting up a custom API server.

In this post, we’ll take a look at how to get started using FaunaDB to create APIs that can be used directly in React applications.

What are we building?

Here is what the final app is going to look like:

expense tracker app

For learning purposes, I am going to keep the UI minimal.

Prerequisites

To take full advantage of this tutorial, make sure you have the following installed on your local development environment:

  • Node.js version >= 12.x.x installed
  • Have access to one package manager such as npm or yarn
  • create-react-app CLI installed as a global package or use npx
  • Access to FaunaDB dashboard
  • Basic knowledge of React Hooks

Why FaunaDB?

Here are some points to consider when using FaunaDB:

  • It supports transactions and follows the ACID (Atomicity, Consistency, Isolation, Durability) convention
  • It scales well. The architecture of FaunaDB lets the database scale according to the demand and load balancing. The database instance is distributed among many instances all over the world such that the users of your application will always have access to the closest instance
  • The data is stored as enhanced JSON documents and such that a document can have nested complex references
  • Another reason why FaunaDB works well with React apps is it is easy to use overall. As a developer or a team of developers, you can focus on building your application instead of setting up and running a backend environment to create APIs
  • It has a GraphQL playground in its dashboard that you can use to import your GraphQL schema using a .gql or .graphql file

Getting started with FaunaDB

Start by registering a new account either using email credentials or using a GitHub account. You can register a new account here:

faunadb signin page

Once you have created a new account or signed in, you are going to be welcomed by the dashboard screen:

That’s it! Now we have our account set up.

Creating a new FaunaDB instance

To create a new database instance using FaunaDB services, you have to follow some simple steps. On the dashboard screen, press the button New Database:

create a new faunadb instance

Then, enter the name of the database and press the button Save:

database name field

Once the database instance is set up, you are ready to create an access key. The access key is to connect authorization and a connection to the database from the React app. This key allows you to control who has the read and write operations to the database.

To create the first access key, from the side menu, go to the Security tab and click on the button New Key:

new key security

Once you press Save on the below screen, a new key will be generated:

new key generated in faunadb dashboard

Do note that the key you are creating right now gives privileges to anyone who has access to it. This means someone who has this key will have access to the database instance. You might want to reconsider this role when creating access keys for production-level apps. (You can choose the other option called server that provides less access):

security key

To avoid tokens being leaked from the React app when storing the project over GitHub, create a .env file inside the root of the React project and paste the access key at the place of YOUR_KEY_HERE in the below snippet:

DB_KEY=YOUR_KEY_HERE

Make sure to add this file in .gitignore. Now, let’s create a new data collection from the FaunaDB dashboard. Go to the Collections tab from the side menu and press the button New Collection:

collections tab

Enter the name of the collection and press the Save button:

collection name

The last step is to create a new index. Go to the Indexes tab to create an index. Searching documents in FaunaDB can be done by using indexes, specifically by matching inputs against an index’s terms field. This will be helpful if you are looking forward to having features like searching on a particular screen:

indexes tab

That’s it. Now let’s begin the process of connecting the React app to Fauna DB and start building it.

Creating a new React app and installing dependencies

To integrate FaunaDB SDK in a React app, first, let us create a new React project. Then, install the required dependencies. To quickly set up a UI, let’s use the Material UI library for React apps. You can replace the UI components library of your own choice:

npx create-react-app expense-tracker-FaunaDB
# after the project directory is created
cd expense-tracker-FaunaDB
# install dependencies
yarn add faunadb @material-ui/core

The faunadb package is FaunaDB JavaScript driver for FaunaDB. The @material-ui/core is the UI library based on the Material design system that has built-in React components that can be used directly in React apps.

To integrate @material-ui/core package in the React app, make sure you follow the instructions guide from the official docs here. After this step, the package.json file is going to be updated with the following dependencies:

{
  "faunadb": "2.14.2",
  "@material-ui/core": "4.10.2"
}

Integrating FaunaDB SDK in a React app

To integrate the faunadb client with the React app, you have to make an initial connection from the app. Create a new file db.js at the directory path src/config/. Then import the faunadb driver and define a new client.

The secret passed as the argument to the faunadb.Client() method is going to hold the access key from .env file:

import faunadb from 'faunadb';
const client = new faunadb.Client({
  secret: process.env.DB_KEY
});
const q = faunadb.query;
export { client, q };

And that’s it for the initial integration.



Creating a navigation bar with Material UI

Start by creating a new components/ directory inside the src/ directory. This directory is going to contain most of the components that are going to be used in the demo React app in this tutorial. Create a file inside the directory and let’s call it Navigation.js.

import { React } from 'react';
export default function Navigation() {
  // ...
}

This component file is going to be responsible for displaying a navigation bar with the title of the app. The title of the app is going to be passed as a prop to this component from the parent component (the App component in the current case).

To build a navigation bar let’s use some pre-defined UI components from the Material UI library:

// other import statements
import { AppBar, Toolbar, Typography } from '@material-ui/core';

The AppBar component is used to display branding, screen titles, and navigation of the web app. That is what we are going to use it for. The ToolBar component is wrapper where you can place your components horizontally. The Typography component applies the Material UI theme that is available by default.

The title prop is going to be the title of the app or the name of the app:

export default function Navigation({ title }) {
  return (
    <AppBar>
      <Toolbar>
        <Typography variant="h6">{title}</Typography>
      </Toolbar>
    </AppBar>
  );
}

In the code snippet above, notice the variant attribute on the Typography component. It uses the variant mapping to properly associate a UI element with an HTML semantic element (such as h6 in the code snippet).

To see it in action, import the Navbar component in the App.js file:

import React from 'react';
import Navigation from './components/Navigation';
function App() {
  return <Navigation title="Expense Tracker app" />;
}
export default App;

From the terminal window, start the development server and open the URL http://localhost:3000 in a browser window. Here is the result:

local host expense tracker

Layout components using grid

To layout React components when using the Material UI library, a common approach to take is to use the Grid component. To start, import this component inside the App component as shown below:

import { Grid } from '@material-ui/core';

This component provides control over the size of the custom components that it wraps around as well as their spacing. Material Design’s responsive UI is based on a 12-column grid layout. The Grid component helps you implement this layout system and then provide the values or the number of grids you want to display. Material UI uses CSS’s Flexbox to manage layout alignments and sizing of the grid columns. A basic grid might look like this:

CSS flexbox layout

How does this Grid component work though? Well, you start by defining a parent Grid component that has an attribute called container which behaves like a flex container. Inside it, you can define other Grid components that are going to have another attribute called item. The basic column width breakpoints such as xs, md, and so on can be applied at each item.

The breakpoints are used internally in various components to make them responsive. You can read more about them in the official documentation here.

Look at the example of how to set two paragraphs aligned adjacent to each other using Grid components. Each item component has some dummy text to display. The makeStyles comes from the Material UI API. It links a style sheet with a function component and returns a Hook that you use inside the component by using the object names as the value to a className attribute.

import React from 'react';
import { Grid, Typography, makeStyles } from '@material-ui/core';
import Navigation from './components/Navigation';
const useStyles = makeStyles({
  root: {
    marginTop: 80,
    margin: 20,
    flexGrow: 1
  }
});
function App() {
  const classes = useStyles();
  return (
    <>
      <Navigation title="Expense Tracker app" />
      <Grid container className={classes.root}>
        <Grid item xs={12} md={6}>
          <Typography variant="p">
            Lorem ipsum dolor sit, amet consectetur adipisicing elit.
            Reprehenderit itaque iusto perferendis consectetur culpa
            accusantium! Dolor, nemo natus ducimus esse minus, ut laborum
            excepturi deserunt recusandae praesentium eligendi consectetur
            labore.
          </Typography>
        </Grid>
        <Grid item xs={12} md={6}>
          <Typography variant="p">
            Lorem ipsum dolor sit, amet consectetur adipisicing elit.
            Reprehenderit itaque iusto perferendis consectetur culpa
            accusantium! Dolor, nemo natus ducimus esse minus, ut laborum
            excepturi deserunt recusandae praesentium eligendi consectetur
            labore.
          </Typography>
        </Grid>
      </Grid>
    </>
  );
}
export default App;

On a wider screen, this is how the breakpoint md is going to display the content:
displayed content

On a smaller screen, the breakpoint xs is going to display the content as shown below:

small screen content display

Now that you have the basic idea of how Material UI works, let’s set up the core UI of the React app.

Building the UI of the expense app

In this section, let us first add some UI using the Material UI library components to display an input field where the user input will go to add a new expense. Also, add a button next to the input field to trigger the action when adding the expense to the Expense Tracker list. Inside App.js file, import the following statements:

import React, { useState } from 'react';
import {
  Paper,
  Grid,
  Button,
  Typography,
  TextField,
  makeStyles
} from '@material-ui/core';

Inside the App component create a state variable called expenseName using useState React Hooks with a default value of an empty string. It is going to store the value of the user input, that is, the details of the expense.

Then define a second state variable with a default value of an array, called expenses that is going to store all the expense detail items:

function App() {
  const classes = useStyles();
  const [expenseDetail, setExpenseDetail] = useState('');
  const [expenses, setExpenses] = useState([]);
  // ...
}

Then, define three handler functions: handleExpenseDetailChange, handleSubmit, resetInputField.

The first handler function is going to update the value of the user input in the state variable expenseDetail.

The second function is going to make a request to the database to store values in the database. Let’s define the contents of this function in a later section.

The third handler method is going to reset the input field back to the empty and shows placeholder value on the UI:

function App() {
  // ...
  function handleExpenseDetailChange(event) {
    console.log(event.target.value);
    setExpenseDetail(event.target.value);
  }
  function handleSubmit(event) {
    event.preventDefault();
  }
  function resetInputField() {
    setExpenseDetail('');
  }
  // ...
}

Lastly, define the input field as well as the button to add the expense detail using visual components from Material UI. Also, add some styling for the list of expenses:

const useStyles = makeStyles({
  root: {
    marginTop: 80,
    margin: 20,
    flexGrow: 1
  },
  list: {
    marginTop: 20
  }
});
function App() {
  // ...
  return (
    <>
      <Navigation title="Expense Tracker app" />
      <Grid container className={classes.root} spacing={3}>
        <Grid item xs={12} md={6}>
          <Paper style={{ margin: 16, padding: 16 }}>
            <Grid container>
              <Grid xs={10} md={11} item style={{ paddingRight: 16 }}>
                <TextField
                  type="text"
                  name={expenseDetail}
                  value={expenseDetail}
                  placeholder="Add your expense here"
                  fullWidth
                  onChange={handleExpenseDetailChange}
                />
              </Grid>
              <Grid xs={2} md={1} item>
                <Button
                  fullWidth
                  color="secondary"
                  variant="outlined"
                  onClick={handleSubmit}
                >
                  Add
                </Button>
              </Grid>
            </Grid>
          </Paper>
        </Grid>
        <Grid item xs={12} md={5} className={classes.list}>
          <Typography>List of Expenses</Typography>
        </Grid>
      </Grid>
    </>
  );
}

Here is the result you are going to get in the browser window after this step:

browser window after this step

add $250 flight cost to expense app

To make sure the value of the user input is getting logged, let us go to the browser window and test it out. Make sure to have Developer Tools -> Console tab open.

input in console log

Creating an API by writing queries

Since our expense list app is going to have some basic CRUD operations, let us create a new directory called src/api/ and inside a new file called index.js. Make sure to import the client and the query instance from the config/db.js file:

import { client, q } from '../config/db';
export const getAllExpenses;
export const createExpenseItem;
export const deleteExpenseItem;

All the three variables that are being exported in from this file are the queries. Let us define them. The first query is to read all the items from the database collection.

To query the list of expenses in the database collection, you need to use the all_expenses index. The query below is going to return a ref that can be mapped over to get the results. Make sure to add the catch such that if there is an error while running the query, it can be logged out:

export const getAllExpenses = client
  .query(q.Paginate(q.Match(q.Ref('indexes/all_expenses'))))
  .then(response => {
    const expenseRef = response.data;
    const getAllDataQuery = expenseRef.map(ref => {
      return q.Get(ref);
    });
    return client.query(getAllDataQuery).then(data => data);
  })
  .catch(error => console.error('Error: ', error.message));

The next query is to create a new item (document) in the expenses collection:

export const createExpenseItem = name =>
  client
    .query(
      q.Create(q.Collection('expenses'), {
        data: {
          name
        }
      })
    )
    .then(ret => ret)
    .catch(error => console.error('Error: ', error.message));

To delete an item from the database collection, all you have to do is refer to the expense ID you want to delete:

export const deleteExpenseItem = expenseId =>
  client
    .query(q.Delete(q.Ref(q.Collection('expenses'), expenseId)))
    .then(ret => ret)
    .catch(error => console.error('Error: ', error.message));

Adding an expense item to FaunaDB

To add the expense details into the FaunaDB collection, import the API function createExpenseItem in the App.js file:

import { createExpenseItem } from './api';

Then, modify the handleSubmit method and invoke the API function createExpenseItem and pass the expenseDetail state variable as the only argument.

function handleSubmit(event) {
  event.preventDefault();
  createExpenseItem(expenseDetail).then(res => {
    console.log('Expense details added to the database');
  });
  resetInputField();
}

Now, when you try to add a new expense, the database collection is going to get an update too:
update in database shown in console log

Here is the up to date database collection. The FaunaDB database takes care of creating a unique id for each document:

updated database dashboard

Displaying the list of expense items from the database

To display a list of expenses from the FaunaDB collection, all you have to do is invoke the query getAllExpenses from the ./api/index.js the file inside useEffect Hook. This is because when the App component renders for the first time, it displays the data as a list of items if there are any in the database:

import { getAllExpenses, createExpenseItem, deleteExpenseItem } from './api';

Since the data from the collection is going to be displayed in a list format, let us import all the necessary components to display a list of items as well as update other import statements:

import React, { useState, useEffect } from 'react';
import {
  Paper,
  Grid,
  Button,
  Typography,
  TextField,
  makeStyles,
  List,
  ListItem,
  ListItemText,
  ListItemSecondaryAction
} from '@material-ui/core';

Inside the App component, define the useEffect Hook before all the handler functions, to fetch the data on the initial render:

function App() {
  // ...
  useEffect(() => {
    getAllExpenses.then(res => {
      setExpenses(res);
      console.log(res);
    });
  }, []);
  // ...
}

Here is how the structure of the data array fetched from the database looks like:

data array fetched from the database

You can see that the ID is presented by ref.id.

Now, add the following JSX after the input field to display a list of items. To display these expenses in a list, let us map the expenses array using JavaScript map function only when the expenses array is not empty:

<Grid item xs={12} md={5} className={classes.list}>
  <Typography>List of Expenses</Typography>
  {expenses &&
    expenses.map(expense => (
      <div key={expense.ref.id}>
        <Paper style={{ margin: 16 }}>
          <List style={{ overflow: 'scroll' }}>
            <ListItem>
              <ListItemText primary={expense.data.name} />
              <ListItemSecondaryAction>
                <Button
                  color="primary"
                  // onClick={}
                >
                  Delete
                </Button>
              </ListItemSecondaryAction>
            </ListItem>
          </List>
        </Paper>
      </div>
    ))}
</Grid>

Here is the output you are going to get after this step:
displayed list of expenses

Deleting an expense item from the list

To delete an expense item, let’s add the handler function handleDeleteItem to delete the item from the database as well as the UI:

function App() {
  // ...
  function handleDeleteItem(event, id) {
    event.preventDefault();
    deleteExpenseItem(id).then(res => res);
    const newExpenses = expenses.filter(expense => expense.ref.id !== id);
    setExpenses(newExpenses);
  }
  // ...
}

Also, to trigger this method, add it to the <Button> inside the <List> component:

<Button
  color="primary"
  onClick={event => handleDeleteItem(event, expense.ref.id)}>
  Delete
</Button>

Now, go back to the browser window and try deleting an item. You will notice that the database collection is updated too:

finished app

Conclusion

Congratulations!

I hope by reading this post, you learned something new. The combination of FaunaDB and React is great for SPAs. You can extend this app by using subscriptions to update the UI as soon as the database gets an update with a new piece of information in real-time.

👉 Here is a list of some resources that you might like after reading this post:

 

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Aman Mittal 👨‍💻 Developer 👉 Node.js, React, React Native | Tech Blogger with 1M+ views on Medium

Leave a Reply