React Firebase CRUD with Realtime Database

In this tutorial, I will show you step by step to build a React Firebase CRUD App with Realtime Database.

Related Posts:
React Firestore CRUD App example | Firebase Cloud Firestore
React.js CRUD example to consume Web API

Using Hooks: React Firebase Hooks: CRUD with Realtime Database example


React Firebase CRUD Overview

We’re gonna build an React Firebase App using firebase library in which:

  • Each Tutorial has key, title, description, published status.
  • We can create, retrieve, update, delete Tutorials (CRUD operations) from Firebase Realtime Database

Here are the screenshots:

– Create a new Tutorial:

react-firebase-crud-realtime-database-create

Firebase Realtime Database right after the Operation:

react-firebase-crud-realtime-database-create-db

– Retrieve all Tutorials with details when clicking on a Tutorial:

react-firebase-crud-realtime-database-retrieve-all

– Change status to Published/Pending using Publish/UnPublish button:

react-firebase-crud-realtime-database-update-status

– Update the Tutorial details with Update button:

react-firebase-crud-realtime-database-update

– Delete the Tutorial using Delete button:

react-firebase-crud-realtime-database-delete

– Delete all Tutorials with Remove All button:

react-firebase-crud-realtime-database-delete-all

CRUD Operations using firebase Reference

We’re gonna use instance of firebase.database.Reference to read/write data from the Firebase database.

var tutorialsRef = firebase.database().ref("/tutorials");

– Read list once using once():

tutorialsRef.once('value', function(snapshot) {
  vat tutorials = [];

  snapshot.forEach(function(childSnapshot) {
    var key = childSnapshot.key;
    var data = childSnapshot.val();
    // ...

    tutorials.push({ key: key, title: data.title, description: data.description});
  });
});

– Read List with listening to the data changes using on():

tutorialsRef.on('child_added', function(data) {
  // data.key, data.val().title, data.val().description
});

tutorialsRef.on('child_changed', function(data) {
  // data.key, data.val().title, data.val().description
});

tutorialsRef.on('child_removed', function(data) {
  // data.key, data.val().title, data.val().description
});

– Listening for all value events on a List reference

var onDataChange =tutorialsRef.on('value', function(snapshot) {
  snapshot.forEach(function(childSnapshot) {
    var childKey = childSnapshot.key;
    var childData = childSnapshot.val();
    // ...
  });
});

– Remove the listener using off():

tutorialsRef.off("value", onDataChange);

– Create a new object in List:

tutorialsRef.push({
  title: "bezkoder Tut#1",
  description: "Helpful tutorial"
});

– Update object in List:
+ destructive update using set(): delete everything currently in place, then save the new value

tutorialsRef.child(key).set({
  title: 'zkoder Tut#1',
  description: 'Tut#1 Description'
});

+ non-destructive update using update(): only updates the specified values

tutorialsRef.child(key).update({
  title: 'zkoder new Tut#1'
});

– Delete an object in List:

tutorialsRef.child(key).remove();

– Delete entire List:

tutorialsRef.remove();

Technology

  • React 16
  • firebase 7
  • bootstrap 4

Setup the Firebase Project

Go to Firebase Console, login with your Google Account, then click on Add Project.

You will see the window like this:

react-firebase-crud-realtime-database-create-project

Enter Project name, set Project Id and click on Continue.
Turn off Enable Google Analytics for this project, then click Create Project.

Now, browser turns into following view:

react-firebase-crud-realtime-database-web-app

If you don’t see it, just choose Project Overview.
Click on Web App, you will see:

react-firebase-crud-realtime-database-register-app

Set the nickname and choose Register App for next step.

react-firebase-crud-realtime-database-add-firebase-sdk

Copy the script for later use.

Choose Database in the left (list of Firebase features) -> Realtime Database -> Create Database.

react-firebase-crud-realtime-database-create-database

In this tutorial, we don’t implement Authentication, so let’s choose test mode:

react-firebase-crud-realtime-database-config-rules

Or if you come from another situation, just open Tab Rules, then change .read and .write values to true.

Setup React.js Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-firebase-database-crud

After the process is done. We create additional folders and files like the following tree:


public

src

components

add-tutorial.component.js

tutorial.component.js

tutorials-list.component.js

services

tutorial.service.js

App.css

App.js

firebase.js

index.js

package.json


Let me explain it briefly.

firebase.js configures information to connect with Firebase Project and export Firebase Database service.
services/tutorial.service.js exports TutorialDataService that uses firebase‘s Database Reference to interact with Firebase Database.
– There are 3 components that uses TutorialDataService:

  • add-tutorial for creating new item
  • tutorials-list contains list of items, parent of tutorial
  • tutorial shows item details

App.js contains Browser Router view and navigation bar.

Import Bootstrap to React Firebase CRUD App

Run command: npm install bootstrap.

Open src/App.js and modify the code inside it as following-

import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";

class App extends Component {
  render() {
    // ...
  }
}

export default App;

Add React Router to React Firebase CRUD App

– Run the command: npm install react-router-dom.
– Open src/index.js and wrap App component by BrowserRouter object.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";

import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

serviceWorker.unregister();

Add Navbar to React Firebase CRUD App

Open src/App.js, this App component is the root container for our application, it will contain a navbar, and also, a Switch object with several Route. Each Route points to a React Component.

There are 2 main routes:

  • /add for add-tutorial component
  • /tutorials for tutorials-list component
import React, { Component } from "react";
import { Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";

import AddTutorial from "./components/add-tutorial.component";
import TutorialsList from "./components/tutorials-list.component";

class App extends Component {
  render() {
    return (
      <div>
        <nav className="navbar navbar-expand navbar-dark bg-dark">
          <a href="/tutorials" className="navbar-brand">
            bezKoder
          </a>
          <div className="navbar-nav mr-auto">
            <li className="nav-item">
              <Link to={"/tutorials"} className="nav-link">
                Tutorials
              </Link>
            </li>
            <li className="nav-item">
              <Link to={"/add"} className="nav-link">
                Add
              </Link>
            </li>
          </div>
        </nav>

        <div className="container mt-3">
          <h2>React Firebase Database CRUD</h2>
          <Switch>
            <Route exact path={["/", "/tutorials"]} component={TutorialsList} />
            <Route exact path="/add" component={AddTutorial} />
          </Switch>
        </div>
      </div>
    );
  }
}

export default App;

Integrate Firebase into React App

First run the command: npm install firebase.

Open src/firebase.js, import firebase library and add configuration that we have saved when Popup window was shown:

import * as firebase from "firebase";
import "firebase/database";

let config = {
  apiKey: "xxx",
  authDomain: "bezkoder-firebase.firebaseapp.com",
  databaseURL: "https://bezkoder-firebase.firebaseio.com",
  projectId: "bezkoder-firebase",
  storageBucket: "bezkoder-firebase.appspot.com",
  messagingSenderId: "xxx",
  appId: "xxx",
};

firebase.initializeApp(config);

export default firebase.database();

Don’t forget to export firebase.database.Database service with firebase.database().

Create Data Service

This service will use Firebase Database service to interact with Firebase Realtime Database. It contains necessary functions for CRUD operations.

services/tutorial.service.js

import firebase from "../firebase";

const db = firebase.ref("/tutorials");

class TutorialDataService {
  getAll() {
    return db;
  }

  create(tutorial) {
    return db.push(tutorial);
  }

  update(key, value) {
    return db.child(key).update(value);
  }

  delete(key) {
    return db.child(key).remove();
  }

  deleteAll() {
    return db.remove();
  }
}

export default new TutorialDataService();

Component for creating Object

This component has a Form to submit new Tutorial with 3 fields: title, description & published (false by default). It calls TutorialDataService.create() method.

components/add-tutorial.component.js

import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";

export default class AddTutorial extends Component {
  constructor(props) {
    super(props);
    this.onChangeTitle = this.onChangeTitle.bind(this);
    this.onChangeDescription = this.onChangeDescription.bind(this);
    this.saveTutorial = this.saveTutorial.bind(this);
    this.newTutorial = this.newTutorial.bind(this);

    this.state = {
      title: "",
      description: "",
      published: false,

      submitted: false,
    };
  }

  onChangeTitle(e) {
    this.setState({
      title: e.target.value,
    });
  }

  onChangeDescription(e) {
    this.setState({
      description: e.target.value,
    });
  }

  saveTutorial() {
    let data = {
      title: this.state.title,
      description: this.state.description,
      published: false
    };

    TutorialDataService.create(data)
      .then(() => {
        console.log("Created new item successfully!");
        this.setState({
          submitted: true,
        });
      })
      .catch((e) => {
        console.log(e);
      });
  }

  newTutorial() {
    this.setState({
      title: "",
      description: "",
      published: false,

      submitted: false,
    });
  }

  render() { ... }
}

First, we define the constructor and set initial state, bind this to the different events.

Because there are 2 fields, so we create 2 functions to track the values of the input and set that state for changes. We also have a function to get value of the form (state) and call TutorialDataService.create() method.

For render() method, we check the submitted state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form will display.

export default class AddTutorial extends Component {
  // ...

  render() {
    return (
      <div className="submit-form">
        {this.state.submitted ? (
          <div>
            <h4>You submitted successfully!</h4>
            <button className="btn btn-success" onClick={this.newTutorial}>
              Add
            </button>
          </div>
        ) : (
          <div>
            <div className="form-group">
              <label htmlFor="title">Title</label>
              <input
                type="text"
                className="form-control"
                id="title"
                required
                value={this.state.title}
                onChange={this.onChangeTitle}
                name="title"
              />
            </div>

            <div className="form-group">
              <label htmlFor="description">Description</label>
              <input
                type="text"
                className="form-control"
                id="description"
                required
                value={this.state.description}
                onChange={this.onChangeDescription}
                name="description"
              />
            </div>

            <button onClick={this.saveTutorial} className="btn btn-success">
              Submit
            </button>
          </div>
        )}
      </div>
    );
  }
}

Component for List of Objects

This component has:

  • a tutorials array displayed as a list on the left.
  • a selected Tutorial which is shown on the right.

react-firebase-crud-realtime-database-retrieve-all

So we will have following state:

  • tutorials
  • currentTutorial and currentIndex

We also need to use 3 TutorialDataService methods:

  • getAll()
  • deleteAll()

components/tutorials-list.component.js

import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";

import Tutorial from "./tutorial.component";

export default class TutorialsList extends Component {
  constructor(props) {
    super(props);
    this.refreshList = this.refreshList.bind(this);
    this.setActiveTutorial = this.setActiveTutorial.bind(this);
    this.removeAllTutorials = this.removeAllTutorials.bind(this);
    this.onDataChange = this.onDataChange.bind(this);

    this.state = {
      tutorials: [],
      currentTutorial: null,
      currentIndex: -1,
    };
  }

  componentDidMount() {
    TutorialDataService.getAll().on("value", this.onDataChange);
  }

  componentWillUnmount() {
    TutorialDataService.getAll().off("value", this.onDataChange);
  }

  onDataChange(items) {
    let tutorials = [];

    items.forEach((item) => {
      let key = item.key;
      let data = item.val();
      tutorials.push({
        key: key,
        title: data.title,
        description: data.description,
        published: data.published,
      });
    });

    this.setState({
      tutorials: tutorials,
    });
  }

  refreshList() {
    this.setState({
      currentTutorial: null,
      currentIndex: -1,
    });
  }

  setActiveTutorial(tutorial, index) {
    this.setState({
      currentTutorial: tutorial,
      currentIndex: index,
    });
  }

  removeAllTutorials() {
    TutorialDataService.deleteAll()
      .then(() => {
        this.refreshList();
      })
      .catch((e) => {
        console.log(e);
      });
  }

  render() { ... }
}

In the code above, we add a listener for data value changes in componentDidMount() and detach the listener in componentWillUnmount().

Inside listener function, we get the key and other fields of each item. This key is unique and important for update operation.

We also have refreshList() function for every time delete operation is done.

Let’s continue to implement render() method:

export default class TutorialsList extends Component {
  // ...

  render() {
    const { tutorials, currentTutorial, currentIndex } = this.state;

    return (
      <div className="list row">
        <div className="col-md-6">
          <h4>Tutorials List</h4>

          <ul className="list-group">
            {tutorials &&
              tutorials.map((tutorial, index) => (
                <li
                  className={
                    "list-group-item " +
                    (index === currentIndex ? "active" : "")
                  }
                  onClick={() => this.setActiveTutorial(tutorial, index)}
                  key={index}
                >
                  {tutorial.title}
                </li>
              ))}
          </ul>

          <button
            className="m-3 btn btn-sm btn-danger"
            onClick={this.removeAllTutorials}
          >
            Remove All
          </button>
        </div>
        <div className="col-md-6">
          {currentTutorial ? (
            <Tutorial
              tutorial={currentTutorial}
              refreshList={this.refreshList}
            />
          ) : (
            <div>
              <br />
              <p>Please click on a Tutorial...</p>
            </div>
          )}
        </div>
      </div>
    );
  }
}

You can see that when we click on any item, setActiveTutorial() function will be invoked to change current active Tutorial, which data is passed to tutorial component.

Component for Object details

This component is the child of tutorial-list. It bind tutorial data and invoke refreshList of the parent.

For getting update, delete the Tutorial, we’re gonna use two TutorialDataService methods:

  • update()
  • delete()

components/tutorial.component.ts

import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";

export default class Tutorial extends Component {
  constructor(props) {
    super(props);
    this.onChangeTitle = this.onChangeTitle.bind(this);
    this.onChangeDescription = this.onChangeDescription.bind(this);
    this.updatePublished = this.updatePublished.bind(this);
    this.updateTutorial = this.updateTutorial.bind(this);
    this.deleteTutorial = this.deleteTutorial.bind(this);

    this.state = {
      currentTutorial: {
        key: null,
        title: "",
        description: "",
        published: false,
      },
      message: "",
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    const { tutorial } = nextProps;
    if (prevState.currentTutorial.key !== tutorial.key) {
      return {
        currentTutorial: tutorial,
        message: ""
      };
    }

    return prevState.currentTutorial;
  }

  componentDidMount() {
    this.setState({
      currentTutorial: this.props.tutorial,
    });
  }

  onChangeTitle(e) {
    const title = e.target.value;

    this.setState(function (prevState) {
      return {
        currentTutorial: {
          ...prevState.currentTutorial,
          title: title,
        },
      };
    });
  }

  onChangeDescription(e) {
    const description = e.target.value;

    this.setState((prevState) => ({
      currentTutorial: {
        ...prevState.currentTutorial,
        description: description,
      },
    }));
  }

  updatePublished(status) {
    TutorialDataService.update(this.state.currentTutorial.key, {
      published: status,
    })
      .then(() => {
        this.setState((prevState) => ({
          currentTutorial: {
            ...prevState.currentTutorial,
            published: status,
          },
          message: "The status was updated successfully!",
        }));
      })
      .catch((e) => {
        console.log(e);
      });
  }

  updateTutorial() {
    const data = {
      title: this.state.currentTutorial.title,
      description: this.state.currentTutorial.description,
    };

    TutorialDataService.update(this.state.currentTutorial.key, data)
      .then(() => {
        this.setState({
          message: "The tutorial was updated successfully!",
        });
      })
      .catch((e) => {
        console.log(e);
      });
  }

  deleteTutorial() {
    TutorialDataService.delete(this.state.currentTutorial.key)
      .then(() => {
        this.props.refreshList();
      })
      .catch((e) => {
        console.log(e);
      });
  }

  render() { ... }
}

And this is the code for render() method:

export default class Tutorial extends Component {
  // ...

  render() {
    const { currentTutorial } = this.state;

    return (
      <div>
        <h4>Tutorial</h4>
        {currentTutorial ? (
          <div className="edit-form">
            <form>
              <div className="form-group">
                <label htmlFor="title">Title</label>
                <input
                  type="text"
                  className="form-control"
                  id="title"
                  value={currentTutorial.title}
                  onChange={this.onChangeTitle}
                />
              </div>
              <div className="form-group">
                <label htmlFor="description">Description</label>
                <input
                  type="text"
                  className="form-control"
                  id="description"
                  value={currentTutorial.description}
                  onChange={this.onChangeDescription}
                />
              </div>

              <div className="form-group">
                <label>
                  <strong>Status:</strong>
                </label>
                {currentTutorial.published ? "Published" : "Pending"}
              </div>
            </form>

            {currentTutorial.published ? (
              <button
                className="badge badge-primary mr-2"
                onClick={() => this.updatePublished(false)}
              >
                UnPublish
              </button>
            ) : (
              <button
                className="badge badge-primary mr-2"
                onClick={() => this.updatePublished(true)}
              >
                Publish
              </button>
            )}

            <button
              className="badge badge-danger mr-2"
              onClick={this.deleteTutorial}
            >
              Delete
            </button>

            <button
              type="submit"
              className="badge badge-success"
              onClick={this.updateTutorial}
            >
              Update
            </button>
            <p>{this.state.message}</p>
          </div>
        ) : (
          <div>
            <br />
            <p>Please click on a Tutorial...</p>
          </div>
        )}
      </div>
    );
  }
}

Add CSS style for React Components

Open src/App.css and write some CSS code as following:

.container h2 {
  text-align: center;
  margin: 25px auto;
}

.list {
  text-align: left;
  max-width: 750px;
  margin: auto;
}

.submit-form {
  max-width: 300px;
  margin: auto;
}

.edit-form {
  max-width: 300px;
  margin: auto;
}

Run & Check

You can run this App with command: npm start.

Compiled successfully!

You can now view react-firebase-database-crud in the browser.

  Local:            http://localhost:3000      
  On Your Network:  http://192.168.1.7:3000

Open browser with url: http://localhost:3000/ and check the result.

Conclusion

Today we’ve built React Firebase CRUD Application successfully working with Realtime Database using firebase library. Now we can display, modify, delete object and list at ease.

If you want to use Hooks, please visit:
React Firebase Hooks: CRUD with Realtime Database example

You can also find how to create React HTTP Client for working with Restful API in:
React.js CRUD example to consume Web API

Or Cloud Firestore for serverless:
React Firestore CRUD App example | Firebase Cloud Firestore

Happy learning, see you again!

Further Reading

Fullstack:

Source Code

You can find the complete source code for this tutorial on Github.

One thought to “React Firebase CRUD with Realtime Database”

  1. Great tutorial.
    There are somethings missing from the instructions as the start script is missing.
    I copied the repo instead to follow along.
    Well set out project thanks for sharing.
    Perhaps you can share a link to setting up the project before adding the files?
    Thanks again

Leave a Reply

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