DEV Community

Cover image for CRUD operation on Firebase Realtime database
Kirti
Kirti

Posted on

CRUD operation on Firebase Realtime database

Hey! developers let's develop a basic MyAnimeList in which we will be working on building react app as frontend and for database, we would be using firebase and in this article, we will see how to perform basic crud operation on real-time database.
STEP-1
Let's create a react app
In your cmd write

npx create-react-app MyAnimeList
cd MyAnimeList
npm start
Enter fullscreen mode Exit fullscreen mode

STEP-2
Let's set up a firebase for our MyAnimeList
a. Google search google firebase console and login with your Gmail account.
b. On the top right corner you will see go to console click on it and there you will see add project button click on add project.
c. Fill in the steps
i. Enter your project name(MyAnimeList)
ii. Toggle the button (off) google analytics for firebase
project.
iii. click on create project now.
Wow! you just created your project on firebase now let's add firebase to our web app.
Below the name of your web app, you will see a symbol "</>" click on it and add your app's name to it and click on Also set up Firebase Hosting for this app.
Now click on the register app.
You will get the firebase configuration here copy it we will use it later.

step -3
Install firebase for app

npm install firebase
Enter fullscreen mode Exit fullscreen mode

Now create a file in your src and name it firebase.js
Now paste here the code that we copied above, and create a const variable to initialize firebase and after initializing export it.Code for the same is attached below.

import firebase from 'firebase/compat/app';
import 'firebase/compat/database';

var firebaseConfig = {
    apiKey: "AIzaSyDowJOJykSjXj5tbwybpinypYh7UGV9YGc",
    authDomain: "myanimelist-e637e.firebaseapp.com",
    databaseURL: "https://myanimelist-e637e-default-rtdb.firebaseio.com",
    projectId: "myanimelist-e637e",
    storageBucket: "myanimelist-e637e.appspot.com",
    messagingSenderId: "854247297412",
    appId: "1:854247297412:web:4c977510e0f36b54220dd1"
  };

const fireDb = firebase.initializeApp(firebaseConfig);
export default fireDb.database().ref();
Enter fullscreen mode Exit fullscreen mode

Now, go to console and click on Realtime database and create database choose or leave the location as it and click on start in test mode and enable it.
yippee... we did all the setup part now let's move forward and create our app.
Let's start our app by building the navbar component.
Firstly we would need to install a react-router-dom library for routing it to multiple pages and we can achieve it with browser stack, switch, and route. I also installed toastify for styling.
In app.js
In the navbar, we have a logo at the left and in right we have pages
before jumping it here do create a separate folder named pages and create all the .js files for the pages on which we will be working later and import it here in the app.js.

import React from "react"
import './App.css';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import About from './pages/About';
import Add from './pages/Add';
import Home from './pages/Home';
import View from './pages/View';
import Navbar from "./Components/Navbar";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
function App() {
  return (
    <BrowserRouter>

      <div className="App">
        {/* calling our navbar component where actual logic is there */}
      <Navbar/>
        <ToastContainer position="top-center" />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/add" component={Add} />
           {/* We need an id of the particluar anime which we want to update  and view */}
          <Route path="/update/:id" component={Add} />
          <Route path="/view/:id" component={View} />
          <Route path="/about" component={About} />
        </Switch>
      </div>
    </BrowserRouter>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Now let's work on our navbar component which we imported in our app.js here we are going to use react-hooks like useState and useEffect. We also need link and useLocation from react-router-dom we are using useLocation because we want to highlight that tab of navbar which we are currently on even if visit different pages with our URL.
I have added a proper explanation of each line of the code in the code itself.

import React, { useEffect, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import "./Navbar.css";

const Navbar = () => {
    const [activeTab, setActiveTab] = useState("Home");
     //it gives the location that where we are currently
    const location = useLocation();
   // a useeffect hook to active the tab of particular pathname
    useEffect(() => {
        if (location.pathname === '/')
            setActiveTab("Home")
        else if (location.pathname === '/add')
            setActiveTab("Add")
        else if (location.pathname === '/about')
            setActiveTab("About")
    }, [location]); // we passed location here because we want this hook to work only if there is change in location

    return (
        <div className="header">
            <p className="logo">Otaku</p>
            <div className="header-right">
                <Link to="/">
                    <p className={`${activeTab === "Home" ? "active" : ""}`}
                        onClick={() => setActiveTab("Home")}
                    >
                        Home
                    </p>
                </Link>

                <Link to="/add">
                    <p className={`${activeTab === "Add" ? "active" : ""}`}
                        onClick={() => setActiveTab("Add")}
                    >
                        Add
                    </p>
                </Link>

                <Link to="/about">
                    <p className={`${activeTab === "About" ? "active" : ""}`}
                        onClick={() => setActiveTab("About")}
                    >
                        About
                    </p>
                </Link>
            </div>
        </div>
    );
};

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

Now some styling of Navbar just copy-paste the CSS file or design it in your way not a big fan of CSS!!

.header
{
    overflow: hidden;
    background: linear-gradient(to bottom, black 0%, #497BE8 35%, grey 55%);
}
.header p{
    float: left;
    color: black;
    text-align: center;
    padding: 10px;
    text-decoration: none;
    font-size: 18px;
    line-height: 5px;
    font-weight: bold;
    font-family: cursive;
    border-radius: 2px;
}
.header p.logo
{
    font-size: 25px;
    font-weight: bold;
    color: black;
    font-family: cursive;
}
.header p:hover{
    background-color: black;
    color: grey;
}

.header p.active{
    background-color: indigo;
    color: pink;
}

.header-right
{
    float: right;
}

@media screen and (max-width: 500px)
{
    .header p {
        float: none;
        display: block;
        text-align: left;
    }

    .header-right {
        float: none;
    }
}
Enter fullscreen mode Exit fullscreen mode

Yippeee done with navbar!!
Now let's Move to our next part where we will be creating Add page in which we will be taking users input and pushing it into the database. I Have added the explanation of the lines wherever needed and have also validated the form as you cannot submit empty form.

import React, { useState, useEffect } from 'react';
import fireDb from '../firebase';
import { toast } from 'react-toastify';
import { useHistory } from 'react-router';
import "./Add.css"

const intialstate = {
    name: "",
    genre: "",
    myPersonalRating: ""
}

const Add = () => {
    const [state, setState] = useState(intialstate);
    const { name, genre, myPersonalRating } = state;

    const history = useHistory();

    const handleInputChange = (e) => {
        const { name, value } = e.target;
        setState({ ...state, [name]: value });
    };
    // here is handlesubmit function where an event is passed
    const handleSubmit = (e) => {
        e.preventDefault();
        // validating if any of the fields is emepty than show an error with the use of toastify
        if (!name || !genre || !myPersonalRating) {
            toast.error("All Fields are mandatory")
        }
        else {
            // imp part to push the values in the database with checking the error also if not than display success
            fireDb.child("Anime").push(state, (err) => {
                if (err) {
                    toast.error(err);
                }
                else {
                    toast.success("Added Successfully")
                }
            });
            // here we are adding a delay of 5 sec and than redirectig it to home page after adding 
            setTimeout(() => history.push("/"), 500);
        }
    };
   // below is the form implementation of the three input fields which we are asking from the user
    return (
        <div style={{ marginTop: "100px" }}>
            <form style={{ margin: "auto", padding: "15px", maxWidth: "400px", alignContent: "center" ,
            color: "brown"}}
                onSubmit={handleSubmit}> 
                <label htmlFor="name">Anime Name</label>
                <input type="text" id="name"
                    name="name"
                    placeholder="Anime Name"
                    value={name}
                    onChange={handleInputChange} />

                <label htmlFor="genre">Genre</label>
                <input type="text" id="genre"
                    name="genre"
                    placeholder="Anime genre"
                    value={genre}
                    onChange={handleInputChange} />

                <label htmlFor="MyPersonalRating">MyPersonalRating</label>
                <input type="number" id="myPersonalRating"
                    name="myPersonalRating"
                    placeholder="Rate It"
                    value={myPersonalRating}
                    onChange={handleInputChange} />

                <input type="submit" value='save'  />
            </form>
        </div>
    )
}
export default Add       
Enter fullscreen mode Exit fullscreen mode

It's CSS code

input[type="text"],
input[type="email"],
input[type="number"],
select {
    width: 100%;
    padding: 12px 20px;
    margin: 8px 0;
    display: inline-block;
    border: 1px solid #ccc;
    border-radius: 4px;
    text-align: center;
    font-family: fantasy;
    box-sizing: border-box;
    border-radius: 6px;
}
 input[type="submit"] {
     width: 100%;
     background-color: darkgreen;
     color: white;
     padding: 14px 20px;
     margin: 8px 0;
     border: none;
     border-radius: 4px;
     cursor: pointer;
 }
input[type="submit"]:hover {
    background-color: darkgreen;
}
Enter fullscreen mode Exit fullscreen mode

Done with adding it into the database now let's display it in tabular form on our home page.

import { useEffect, useState } from "react"
import { Link } from "react-router-dom";
import { toast } from "react-toastify";
import fireDb from '../firebase';
import "./Home.css";

const Home = () => {

    const [data, setData] = useState({});

    const onDelete = (id) =>{
        if(window.confirm("Are you sure you want to delete it ?"))
        {
            fireDb.child(`Anime/${id}`).remove((err) =>{
                if(err)
                {
                    toast.error(err);
                }
                else 
                {
                    toast.success("deleting from the database");
                }
            })
        }
    }

    useEffect(() => {
        fireDb.child("Anime").on("value",(snapshot) => {
            if(snapshot.val() !== null)
            {
                setData({...snapshot.val()});
            }
            else
            {
                setData({});
            }
        });
        return () => {
            setData({});
        };
    },[]);

    return (
        <div style={{ marginTop: "100px"}}>
            <table className="styled-table">
                <thead>
                    <tr>
                        <th style={{ textAlign: "center" }}> No</th>
                        <th style={{ textAlign: "center" }}> Name</th>
                        <th style={{ textAlign: "center" }}> Genere</th>
                        <th style={{ textAlign: "center" }}> MyPersonalRating</th>
                        <th style={{ textAlign: "center" }}> Action</th>
                    </tr>
                </thead>
                <tbody>
                    {/* maping the objects we have in database */}
                    {
                        Object.keys(data).map((id, index) => {
                            return (
                                    <tr key={id}>
                                        <th scope="row">
                                            {index+1}
                                        </th>
                                        <td>{data[id].name}</td>
                                        <td>{data[id].genre}</td>
                                        <td>{data[id].myPersonalRating}</td>
                                        <td>
                                            <Link to ={`/update/${id}`}>
                                                <button className="btn btn-edit">Edit</button>
                                            </Link>
                                                <button className="btn btn-delete" onClick={()=> onDelete(id)}>Delete</button>

                                            <Link to ={`/view/${id}`}>
                                                <button className="btn btn-view">View</button>
                                            </Link>
                                            </td>
                                    </tr>
                            );
                        }
                        )
                    }
                </tbody>
            </table>

        </div>
    )
}
export default Home;
Enter fullscreen mode Exit fullscreen mode

It's CSS file

.styled-table{
    border-collapse:collapse;
    margin: auto;
    font-size: 0.9em;
    font-family: sans-serif;
    max-width: 800px;
    box-shadow: 0 0 20px rgba(0,0,0,0.15);
    align-content: center;
}

.styled-table thead tr {
    background-color: darkslategray;
    color: deepskyblue;
    text-align: left;
}

.styled-table th,
.styled-table td{
    padding: 12px 15px;
}

.styled-table tbody tr {
    border-bottom: 1px solid #dddd;
}

.styled-table tbody tr:nth-of-type(even)
{
    background-color: dimgrey;
}

.styled-table tbody tr:last-of-type{
    border-bottom: 2px solid #009879;
}

.btn {
    border: none;
    color: white;
    padding: 5px 8px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    border-radius: 5px;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
}

.btn-edit{
    background-color: yellow;
}

.btn-delete{
    background-color: red;
}

.btn-view{
    background-color: green;
}
Enter fullscreen mode Exit fullscreen mode

Now we will add the working to edit button on the home page. Let's perform an update operation on the real-time database in this we need an id for updating the particular record. Below is the implementation with the comments

import React, { useState, useEffect } from 'react';
import fireDb from '../firebase';
import { toast } from 'react-toastify';
import { useHistory, useParams } from 'react-router';
import "./Add.css"

const intialstate = {
    name: "",
    genre: "",
    myPersonalRating: ""
}

const Add = () => {
    const [state, setState] = useState(intialstate);
    const [data, setData] =useState({});
    const { name, genre, myPersonalRating } = state;

    const {id} =useParams();
    // STEP -1 FIRST LETS POPULATE THE FORM IF DATA IS AVAILABLE and only if id is also there
    useEffect(() => {
      fireDb.child("Anime").on("value",(snapshot) =>
      {
          if(snapshot.val()!==null)
          {
              setData({...snapshot.val()});
          }
          else 
          {
              setData({});
          }
      });
      // CLEANUP
      return () => {
          setData({});
      };
    },[id]);

    useEffect(() => {
        if(id)
        {
           setState({...data[id]});
        }
        else 
        {
            setState({...intialstate});
        }
        return () =>{
            setState({...intialstate});
        };
    },[id,data]);

    const history = useHistory();

    const handleInputChange = (e) => {
        const { name, value } = e.target;
        setState({ ...state, [name]: value });
    };
    // here is handlesubmit function where an event is passed
    const handleSubmit = (e) => {
        e.preventDefault();
        // validating if any of the fields is emepty than show an error with the use of toastify
        if (!name || !genre || !myPersonalRating) {
            toast.error("All Fields are mandatory")
        }
        else {
            // imp part to push the values in the database with checking the error also if not than display success
            if(!id)
            {
                fireDb.child("Anime").push(state, (err) => {
                    if (err) {
                        toast.error(err);
                    }
                    else {
                        toast.success("Added Successfully")
                    }
                });
            }
            // if already exsists and we want to modify it 
            else 
            {
                fireDb.child(`Anime/${id}`).set(state, (err) =>{
                   if(err){
                       toast.error(err);
                   }
                   else 
                   {
                       toast.success("Updated Successfully")
                   }
                }
                )
            }


            // here we are adding a delay of 3 sec and than redirectig it to home page after adding 
            setTimeout(() => history.push("/"), 300);
        }
    };
   // below is the form implementation of the three input fields which we are asking from the user
    return (
        <div style={{ marginTop: "100px" }}>
            <form style={{ margin: "auto", padding: "15px", maxWidth: "400px", alignContent: "center" ,
            color: "brown"}}
                onSubmit={handleSubmit}> 
                <label htmlFor="name">Anime Name</label>
                <input type="text" id="name"
                    name="name"
                    placeholder="Anime Name"
                    value={name || ""}      // value can be empty or already there
                    onChange={handleInputChange} />

                <label htmlFor="genre">Genre</label>
                <input type="text" id="genre"
                    name="genre"
                    placeholder="Anime genre"
                    value={genre || ""}
                    onChange={handleInputChange} />

                <label htmlFor="MyPersonalRating">MyPersonalRating</label>
                <input type="number" id="myPersonalRating"
                    name="myPersonalRating"
                    placeholder="Rate It"
                    value={myPersonalRating || ""}
                    onChange={handleInputChange} />
                {/* If id is there than upadte otherwise save */}
                <input type="submit" value={id ? "update":"save"}/>  
            </form>
        </div>
    )
}

export default Add
Enter fullscreen mode Exit fullscreen mode

Now the last part adds functionality to our view button In this, we will be displaying particular anime details on a separate page.

import React, { useEffect, useState } from 'react'
import fireDb from '../firebase';
import { Link } from "react-router-dom";
import { useParams } from 'react-router';
import './View.css'
const View = () => {

    const [anime,setAnime] = useState({});
    const {id} = useParams();
    useEffect(() => {
        fireDb.child(`Anime/${id}`).get().then((snapshot) => {
            if(snapshot.exists())
            {
                setAnime({...snapshot.val()});
            }
            else 
            {
                setAnime({});
            }
        });
    },[id]);
    console.log("anime",anime);
    return (
        <div style={{marginTop: "150px"}}>
            <div className="card">
                <div className="card-header">
                    <p>Anime Detail</p>
                    </div>
                    <div className="container">
                        <strong>ID: </strong>
                        <span>{id}</span>
                        <br/>
                        <br/>

                        <strong>Anime_Name: </strong>
                        <span>{anime.name}</span>
                        <br/>
                        <br/>

                        <strong>Anime_Genre: </strong>
                        <span>{anime.genre}</span>
                        <br/>
                        <br/>

                        <strong>My_Personal_Rating: </strong>
                        <span>{anime.myPersonalRating}</span>
                        <br/>
                        <br/>
                      <Link to="/">
                          <button className="btn btn-edit"> Head to Home</button>
                      </Link>
                    </div>
              </div>
              </div>
    )
}

export default View

Enter fullscreen mode Exit fullscreen mode

IT's CSS file

.card{
    width: 30%;
    display: flex;
    flex-direction: column;
    border: red solid;
    align-content: center;
    margin: 0 auto;
}

.card-header {
    height: 30%;
    background: black;
    color: grey;
    text-align: center;
}

.card-header p {
    font-size:20px;
}

.container {
    padding: 4px 16px;
}

Enter fullscreen mode Exit fullscreen mode

Let's have a look at how our web app looks like, apologies for the bad styling in advance.

Image description
Thank you!
Github

Discussion (0)