React 17 Firebase + Material UI | Create a TODO App with CRUD Operations using Firebase Database

In this React 16+ tutorial, we are going to integrate Firebase Database service and Material UI library to create a working TODO application having CRUD (Create Read Update and Delete) operations in React Js application. Users can Add, List, Update or Delete todos from a list by communicating with Firebase.

Firebase is very popular for many cloud-based services like Realtime database, Authentication services, Testing, Storage, Crash analysis, Hosting, and many more.

The combination of React and Firebase can move applications to the next level. In this tutorial, we are going to integrate Firebase NoSQL Database service using which a user can do CRUD operations realtime in the application.

To style our React TODO application, we’ll be using Material UI. It is a very popular React specific UI library to add beautifully designed components like Inputs, Buttons, Modal, Grids, Lists, Icons, etc very quickly.

So, with these power packs, let’s get started!

 

 

 

Create a React Application

First, we’ll create a new React application using npx create-react-app command

$ npx create-react-app react-firebase-materialui-todo-app

Move inside the react app

$ cd react-firebase-materialui-todo-app

Run application

$ npm start

 

SignIn Firebase and Get App Credential

To connect our React application with a Firebase application, you need to follow these steps:

# Step 1: SignIn or SignUp Firebase

Register or Login Firebase with your credentials.

# Step 2: Create a new or use existing Firebase application

After login, click on the Create a project button.

Then enter project name, then click Continue

Click Continue again

Finally hit Create project

Bingo!!!

# Step 4: Enable Database and Hosting Services

Now on the left sidebar click on Develop then enable Database and Hosting one by one

Make sure you create a Database in the Test Mode during development.

 

# Step 3: Click on Web icon to create a new application

We are ready with Firebase project, next, we need to create a new Firebase application. For that, go to Project Overview then hit Web icons

 

Enter the Firebase application name then enable Hosting( Optional )

 

# Step 4: Go to Firebase and Check Credential

Click on application’s configuration icon

Under the General tab, scroll down to see Config object

Keep this object handy which we’ll use later in this tutorial.

 

Install Firebase package in React

To connect and control Firebase from React application’s CLI, we’ll install the official firebase package.

$ npm install --save firebase

 

Connect React with Firebase Application

To connect our Firebase application with React application, we need to create a firebase config file inside the React project folder.

Create a new file firebase-config.js inside the src at the root folder. Update this file with the following code.

// src/firebase-config.js

import firebase from 'firebase';

const firebaseApp = firebase.initializeApp({
    apiKey: "AIzaSyBMgYcu4ifbQd5o29cfYbLuZ4SWYfdYHuI",
    authDomain: "freakyjolly-c91ee.firebaseapp.com",
    databaseURL: "https://freakyjolly-c91ee.firebaseio.com",
    projectId: "freakyjolly-c91ee",
    storageBucket: "freakyjolly-c91ee.appspot.com",
    messagingSenderId: "997516632089",
    appId: "1:997516632089:web:46746c6f9815f43e2c4a88",
    measurementId: "G-V6X4RQ389S"
});

const db = firebaseApp.firestore();
export default db;

We have to import the firebase module using which we initialized the React application to connect with Firebase application credential object.

 

Install Material UI package

We are going to style out the React ToDo application using the Material UI component. Run the following npm command to install the Material UI core and Icons package.

$ npm install @material-ui/core @material-ui/icons

 

Finally Start Building the TODO Application

We’ll be using various Material UI components like List, Dialog, Icons, Button, Input etc to create nice looking layout.

To keep our application simple, we’ll have everything in the App.js functional component using Hooks and methods. But you can have separate components for Todo input, List and Dialog modal to update.

For performing CRUD operations, the Firebase library methods will be used to Add, List, Update and Delete Todo’s.

 

# Add Input and Button to Create New TODO

Import the firebase-config we created in previous steps and firebase

import db from './firebase-config'
import firebase from 'firebase';

 

Also, import the icons from the Material UI icons package. Currently, we have Add icon. The components also need to be imported as well

import { AddCircleOutlineRounded } from '@material-ui/icons';
import { Button, TextField, Container } from '@material-ui/core';

 

For adding and maintain the Todo object, add two useState hook methods.

const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');

 

To get a real-time snapshot of our todos list from Firestore collection, we’ll add the useEffect hook.

useEffect(() => {
    console.log('useEffect Hook!!!');

    db.collection('todos').orderBy('datetime', 'desc').onSnapshot(snapshot => {
      console.log('Firebase Snap!');
      setTodos(snapshot.docs.map(doc => {
        return {
          id: doc.id,
          name: doc.data().todo,
          datatime: doc.data().datatime
        }
      }))
    })

  }, []);

The setTodos() hook method is updating todos array with updated snapshot using map() method.

On the Button, we will have an onClick event handler to call addTodo function

const addTodo = (event) => {
    event.preventDefault();
    db.collection('todos').add({
      todo: input,
      datetime: firebase.firestore.FieldValue.serverTimestamp()
    })
    setInput('');
  }

We have also used the firestore’s FieldValue to assign datetime with server time.

To create a form using Material UI, add the following template inside the App function’s return

return (
    <Container maxWidth="sm">

      <form noValidate>

        <TextField
          variant="outlined"
          margin="normal"
          required
          fullWidth
          id="todo"
          label="Enter ToDo"
          name="todo"
          autoFocus
          value={input}
          onChange={event => setInput(event.target.value)}
        />

        <Button
          type="submit"
          variant="contained"
          color="primary"
          fullWidth
          onClick={addTodo}
          disabled={!input}
          startIcon={<AddCircleOutlineRounded />}
        >
          Add Todo
      </Button>

      </form>


    </Container >
  );

This will create a nice layout with Button disabled when no input is typed by user.

 

 

 

 

 

# List & Delete Todos

To create the list with delete and update icon, we’ll use the Material UI component.

import { AddCircleOutlineRounded, DeleteOutlineRounded, Edit } from '@material-ui/icons';
import { Button, TextField, Container, IconButton, List, ListItem, ListItemSecondaryAction, ListItemText, Dialog, DialogContent, DialogActions } from '@material-ui/core';

 

Add the deleteTodo function to remove todo from Firestore using the delete() method.

const deleteTodo = (id) => {
    db.collection('todos').doc(id).delete().then(res => {
      console.log('Deleted!', res);
    });
  }

Also, add List component in the return() after form

<List dense={true}>
        {
          todos.map(todo => (

            <ListItem key={todo.id} >

              <ListItemText
                primary={todo.name}
                secondary={todo.datetime}
              />

              <ListItemSecondaryAction>
                <IconButton edge="end" aria-label="Edit" onClick={() => openUpdateDialog(todo)}>
                  <Edit />
                </IconButton>
                <IconButton edge="end" aria-label="delete" onClick={() => deleteTodo(todo.id)}>
                  <DeleteOutlineRounded />
                </IconButton>
              </ListItemSecondaryAction>

            </ListItem>
          ))
        }
      </List>

# Update Todo using Dialog Modal

As we are doing all stuff in the App itself, so we need two more hooks for updating the todo

const [open, setOpen] = useState(false);
 const [update, setUpdate] = useState('');
 const [toUpdateId, setToUpdateId] = useState('');

First will control the Dialog open and close state. The second will be used for the Input control inside the Dialog. The third hook is getting used to keeping the id of todo which we are going to update.

Update a list todo item using the Dialog modal box. Add the Dialog Material UI component

<Dialog open={open} onClose={handleClose}>
        <DialogContent>
          <TextField
            autoFocus
            margin="normal"
            label="Update Todo"
            type="text"
            fullWidth
            name="updateTodo"
            value={update}
            onChange={event => setUpdate(event.target.value)}
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose} color="primary">
            Cancel
          </Button>
          <Button onClick={editTodo} color="primary">
            Save
          </Button>
        </DialogActions>
      </Dialog>

 

Finally, our App.js file will have the following code

import React, { useState, useEffect } from 'react';
import './App.css';
import db from './firebase-config'
import firebase from 'firebase';

import { AddCircleOutlineRounded, DeleteOutlineRounded, Edit } from '@material-ui/icons';

import { Button, TextField, Container, IconButton, List, ListItem, ListItemSecondaryAction, ListItemText, Dialog, DialogContent, DialogActions } from '@material-ui/core';


function App() {

  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  const [open, setOpen] = useState(false);
  const [update, setUpdate] = useState('');
  const [toUpdateId, setToUpdateId] = useState('');


  useEffect(() => {
    console.log('useEffect Hook!!!');

    db.collection('todos').orderBy('datetime', 'desc').onSnapshot(snapshot => {
      console.log('Firebase Snap!');
      setTodos(snapshot.docs.map(doc => {
        return {
          id: doc.id,
          name: doc.data().todo,
          datatime: doc.data().datatime
        }
      }))
    })

  }, []);

  const addTodo = (event) => {
    event.preventDefault();
    db.collection('todos').add({
      todo: input,
      datetime: firebase.firestore.FieldValue.serverTimestamp()
    })
    setInput('');
  }

  const deleteTodo = (id) => {
    db.collection('todos').doc(id).delete().then(res => {
      console.log('Deleted!', res);
    });
  }

  const openUpdateDialog = (todo) => {
    setOpen(true);
    setToUpdateId(todo.id);
    setUpdate(todo.name);
  }

  const editTodo = () => {
    db.collection('todos').doc(toUpdateId).update({
      todo: update
    });
    setOpen(false);
  }

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <Container maxWidth="sm">

      <form noValidate>

        <TextField
          variant="outlined"
          margin="normal"
          required
          fullWidth
          id="todo"
          label="Enter ToDo"
          name="todo"
          autoFocus
          value={input}
          onChange={event => setInput(event.target.value)}
        />

        <Button
          type="submit"
          variant="contained"
          color="primary"
          fullWidth
          onClick={addTodo}
          disabled={!input}
          startIcon={<AddCircleOutlineRounded />}
        >
          Add Todo
      </Button>

      </form>

      <List dense={true}>
        {
          todos.map(todo => (

            <ListItem key={todo.id} >

              <ListItemText
                primary={todo.name}
                secondary={todo.datetime}
              />

              <ListItemSecondaryAction>
                <IconButton edge="end" aria-label="Edit" onClick={() => openUpdateDialog(todo)}>
                  <Edit />
                </IconButton>
                <IconButton edge="end" aria-label="delete" onClick={() => deleteTodo(todo.id)}>
                  <DeleteOutlineRounded />
                </IconButton>
              </ListItemSecondaryAction>

            </ListItem>
          ))
        }
      </List>

      <Dialog open={open} onClose={handleClose}>
        <DialogContent>
          <TextField
            autoFocus
            margin="normal"
            label="Update Todo"
            type="text"
            fullWidth
            name="updateTodo"
            value={update}
            onChange={event => setUpdate(event.target.value)}
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose} color="primary">
            Cancel
          </Button>
          <Button onClick={editTodo} color="primary">
            Save
          </Button>
        </DialogActions>
      </Dialog>


    </Container >
  );
}

export default App;

That’s it now you can run the application by hitting $ npm start

 

Source Code

Find source code in the GitHub repository here.

 

Conclusion

In the above tutorial, we got to learn how to easily integrate Firebase services in the React application. We used Firestore’s real-time NoSQL database to return todos collection inside the useEffect hook to create a list.

For deleting and updating the todo we used material icons and called functions to perform CRUD operations.

Let us know if it helped… do share your feedback

Stay Safe!

1 thought on “React 17 Firebase + Material UI | Create a TODO App with CRUD Operations using Firebase Database”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top