In this tutorial, I will show you step by step to build a React Firebase CRUD App using Hooks with Realtime Database.
Related Posts:
– React Custom Hook tutorial with example
– React Hooks CRUD example with Axios and Web API
– React Form Validation with Hooks example
– React Hooks Firestore example: Build a CRUD app
– React Component: React Firebase CRUD with Realtime Database
Contents
- Overview of React Firebase using Hooks CRUD App
- CRUD Operations using firebase Reference
- react-firebase-hooks library
- Technology
- Setup the Firebase Project
- Setup React Hooks Project
- Import Bootstrap to React Firebase CRUD App
- Add React Router to React Firebase CRUD App
- Add Navbar to React Firebase CRUD App
- Integrate Firebase into React App
- Create Data Service
- Page for creating Object
- Page for List of Objects
- Using react-firebase-hooks instead
- Page for Object details
- Add CSS style for React Components
- Run & Check
- Conclusion
- Further Reading
- Source Code
Overview of React Firebase using Hooks CRUD App
We’re gonna build an React Hooks 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:
Firebase Realtime Database right after the Operation:
– Retrieve all Tutorials with details when clicking on a Tutorial:
– Change status to Published/Pending using Publish/UnPublish button:
– Update the Tutorial details with Update button:
– Delete the Tutorial using Delete button:
– Delete all Tutorials with Remove All button:
If you need Form Validation with React Hook Form 7, please visit:
React Form Validation with Hooks example
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: "Tutorial 1 Description"
});
– 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 new 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();
react-firebase-hooks library
react-firebase-hooks provides listeners for Realtime Database lists and values. These hooks wrap around the firebase.database().ref('tutorials').on()
method:
- useList
- useListKeys
- useListVals
- useObject
- useObjectVal
There are also an error
and loading
property for supporting UI display and message.
For example, I will show you how to work with useList
.
– First we import the hook from react-firebase-hooks/database
:
import { useList } from "react-firebase-hooks/database";
– Next we retrieve and monitor the list value:
var tutorialsRef = firebase.database().ref("/tutorials");
const [snapshots, loading, error] = useList(tutorialsRef );
snapshots
: an array offirebase.database.DataSnapshot
loading
: a boolean for loading statuserror
: afirebase.FirebaseError
when trying to load the data
– Finally, we can display the properties on template:
{error && <strong>Error: {error}</strong>}
{loading && <span>Loading...</span>}
<ul>
{!loading && snapshots &&
snapshots.map((tutorial, index) => (
<li>
{tutorial.val().title}
</li>
))}
</ul>
You can find more details about how to use other hooks here.
Technology
- React 16
- firebase 7
- react-firebase-hooks 2
- 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:
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:
If you don’t see it, just choose Project Overview.
Click on Web App, you will see:
Set the nickname and choose Register App for next step.
Copy the script for later use.
Choose Database in the left (list of Firebase features) -> Realtime Database -> Create Database.
In this tutorial, we don’t implement Authentication, so let’s choose test mode:
Or if you come from another situation, just open Tab Rules, then change .read
and .write
values to true
.
Setup React Hooks Project
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-firebase-hooks-crud
After the process is done. We create additional folders and files like the following tree:
public
src
components
AddTutorial.js
Tutorial.js
TutorialsList.js
services
TutorialService.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 pages that uses TutorialDataService
:
AddTutorial
for creating new itemTutorialsList
contains list of items, parent oftutorial
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 from "react";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
return (
// ...
);
}
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();
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 Page.
There are 2 main routes:
/add
forAddTutorial
page/tutorials
forTutorialsList
page
import React from "react";
import { Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import AddTutorial from "./components/AddTutorial";
import TutorialsList from "./components/TutorialsList";
function App() {
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 Hooks 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/TutorialService.js
import firebase from "../firebase";
const db = firebase.ref("/tutorials");
const getAll = () => {
return db;
};
const create = (data) => {
return db.push(data);
};
const update = (key, data) => {
return db.child(key).update(data);
};
const remove = (key) => {
return db.child(key).remove();
};
const removeAll = () => {
return db.remove();
};
export default {
getAll,
create,
update,
remove,
removeAll,
};
Page for creating Object
This page has a Form to submit new Tutorial with 3 fields: title
, description
& published
(false by default). It calls TutorialDataService.create()
method.
components/AddTutorial.js
import React, { useState } from "react";
import TutorialDataService from "../services/TutorialService";
const AddTutorial = () => {
const initialTutorialState = {
title: "",
description: "",
published: false
};
const [tutorial, setTutorial] = useState(initialTutorialState);
const [submitted, setSubmitted] = useState(false);
const handleInputChange = event => {
const { name, value } = event.target;
setTutorial({ ...tutorial, [name]: value });
};
const saveTutorial = () => {
var data = {
title: tutorial.title,
description: tutorial.description,
published: false
};
TutorialDataService.create(data)
.then(() => {
setSubmitted(true);
})
.catch(e => {
console.log(e);
});
};
const newTutorial = () => {
setTutorial(initialTutorialState);
setSubmitted(false);
};
return (
// ...
);
};
export default AddTutorial;
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.
const AddTutorial = () => {
...
return (
<div className="submit-form">
{submitted ? (
<div>
<h4>You submitted successfully!</h4>
<button className="btn btn-success" onClick={newTutorial}>
Add
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
required
value={tutorial.title}
onChange={handleInputChange}
name="title"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
required
value={tutorial.description}
onChange={handleInputChange}
name="description"
/>
</div>
<button onClick={saveTutorial} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
);
};
export default AddTutorial;
Page for List of Objects
This page has:
- a tutorials array displayed as a list on the left.
- a selected Tutorial which is shown on the right.
So we will have following state:
tutorials
currentTutorial
andcurrentIndex
We also need to use 2 TutorialDataService
methods:
getAll()
deleteAll()
components/TutorialsList.js
import React, { useState, useEffect } from "react";
import TutorialDataService from "../services/TutorialService";
import Tutorial from "./Tutorial";
const TutorialsList = () => {
const [tutorials, setTutorials] = useState([]);
const [currentTutorial, setCurrentTutorial] = useState(null);
const [currentIndex, setCurrentIndex] = useState(-1);
const 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,
});
});
setTutorials(tutorials);
};
useEffect(() => {
TutorialDataService.getAll().on("value", onDataChange);
return () => {
TutorialDataService.getAll().off("value", onDataChange);
};
}, []);
const refreshList = () => {
setCurrentTutorial(null);
setCurrentIndex(-1);
};
const setActiveTutorial = (tutorial, index) => {
const { title, description, published } = tutorial;
setCurrentTutorial({
key: tutorial.key,
title,
description,
published,
});
setCurrentIndex(index);
};
const removeAllTutorials = () => {
TutorialDataService.removeAll()
.then(() => {
refreshList();
})
.catch((e) => {
console.log(e);
});
};
return (
// ...
);
};
export default TutorialsList;
In the code above, we add a listener for data value changes and detach the listener using useEffect()
hook.
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:
const TutorialsList = () => {
...
return (
<div className="list row">
<div className="col-md-6">
<h4>Tutorials List</h4>
<ul className="list-group">
{tutorials.map((tutorial, index) => (
<li
className={"list-group-item " + (index === currentIndex ? "active" : "")}
onClick={() => setActiveTutorial(tutorial, index)}
key={index}
>
{tutorial.title}
</li>
))}
</ul>
<button
className="m-3 btn btn-sm btn-danger"
onClick={removeAllTutorials}
>
Remove All
</button>
</div>
<div className="col-md-6">
{currentTutorial ? (
<Tutorial tutorial={currentTutorial} refreshList={refreshList} />
) : (
<div>
<br />
<p>Please click on a Tutorial...</p>
</div>
)}
</div>
</div>
);
};
export default TutorialsList;
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.
Using react-firebase-hooks instead
Let’s modify the TutorialsList
page to take advantage of react-firebase-hooks useList()
hook.
You have error
and loading
property to give a complete lifecycle for loading and listening to the Database Reference, but don’t need to add/detach listener within useEffect()
hook.
import React, { useState } from "react";
import { useList } from "react-firebase-hooks/database";
import TutorialDataService from "../services/TutorialService";
import Tutorial from "./Tutorial";
const TutorialsList = () => {
const [currentTutorial, setCurrentTutorial] = useState(null);
const [currentIndex, setCurrentIndex] = useState(-1);
/* use react-firebase-hooks */
const [tutorials, loading, error] = useList(TutorialDataService.getAll());
const refreshList = () => {
setCurrentTutorial(null);
setCurrentIndex(-1);
};
const setActiveTutorial = (tutorial, index) => {
const { title, description, published } = tutorial.val();
setCurrentTutorial({
key: tutorial.key,
title,
description,
published,
});
setCurrentIndex(index);
};
const removeAllTutorials = () => {
TutorialDataService.removeAll()
.then(() => {
refreshList();
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="list row">
<div className="col-md-6">
<h4>Tutorials List</h4>
{error && <strong>Error: {error}</strong>}
{loading && <span>Loading...</span>}
<ul className="list-group">
{!loading &&
tutorials &&
tutorials.map((tutorial, index) => (
<li
className={"list-group-item " + (index === currentIndex ? "active" : "")}
onClick={() => setActiveTutorial(tutorial, index)}
key={index}
>
{tutorial.val().title}
</li>
))}
</ul>
<button
className="m-3 btn btn-sm btn-danger"
onClick={removeAllTutorials}
>
Remove All
</button>
</div>
<div className="col-md-6">
{currentTutorial ? (
<Tutorial tutorial={currentTutorial} refreshList={refreshList} />
) : (
<div>
<br />
<p>Please click on a Tutorial...</p>
</div>
)}
</div>
</div>
);
};
export default TutorialsList;
Page for Object details
This page 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.js
import React, { useState } from "react";
import TutorialDataService from "../services/TutorialService";
const Tutorial = (props) => {
const initialTutorialState = {
key: null,
title: "",
description: "",
published: false,
};
const [currentTutorial, setCurrentTutorial] = useState(initialTutorialState);
const [message, setMessage] = useState("");
const { tutorial } = props;
if (currentTutorial.key !== tutorial.key) {
setCurrentTutorial(tutorial);
setMessage("");
}
const handleInputChange = (event) => {
const { name, value } = event.target;
setCurrentTutorial({ ...currentTutorial, [name]: value });
};
const updatePublished = (status) => {
TutorialDataService.update(currentTutorial.key, { published: status })
.then(() => {
setCurrentTutorial({ ...currentTutorial, published: status });
setMessage("The status was updated successfully!");
})
.catch((e) => {
console.log(e);
});
};
const updateTutorial = () => {
const data = {
title: currentTutorial.title,
description: currentTutorial.description,
};
TutorialDataService.update(currentTutorial.key, data)
.then(() => {
setMessage("The tutorial was updated successfully!");
})
.catch((e) => {
console.log(e);
});
};
const deleteTutorial = () => {
TutorialDataService.remove(currentTutorial.key)
.then(() => {
props.refreshList();
})
.catch((e) => {
console.log(e);
});
};
return (
// ...
);
};
export default Tutorial;
And this is the code inside return
:
const Tutorial = (props) => {
...
return (
<div>
{currentTutorial ? (
<div className="edit-form">
<h4>Tutorial</h4>
<form>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
name="title"
value={currentTutorial.title}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
name="description"
value={currentTutorial.description}
onChange={handleInputChange}
/>
</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={() => updatePublished(false)}
>
UnPublish
</button>
) : (
<button
className="badge badge-primary mr-2"
onClick={() => updatePublished(true)}
>
Publish
</button>
)}
<button className="badge badge-danger mr-2" onClick={deleteTutorial}>
Delete
</button>
<button
type="submit"
className="badge badge-success"
onClick={updateTutorial}
>
Update
</button>
<p>{message}</p>
</div>
) : (
<div>
<br />
<p>Please click on a Tutorial...</p>
</div>
)}
</div>
);
};
export default Tutorial;
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-hooks-crud in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.1.6:3000
Open browser with url: http://localhost:3000/
and check the result.
Conclusion
Today we’ve built React Firebase Hooks CRUD Application successfully working with Realtime Database using firebase
library. Now we can display, modify, delete object and list at ease.
For working with Firestore:
React Hooks Firestore example: Build a CRUD app
You can also find how to create React HTTP Client for working with Restful API in:
React Hooks CRUD example with Axios and Web API
Or using React Component instead: React Firebase CRUD with Realtime Database
If you need Form Validation with React Hook Form 7, please visit:
React Form Validation with Hooks example
Happy learning, see you again!
Further Reading
- React Hooks
- React Custom Hook tutorial with example
- firebase.database.Database
- firebase.database.Reference
- Firebase Web Get Started
- react-firebase-hooks
Fullstack:
– React + Spring Boot + MySQL: CRUD example
– React + Spring Boot + PostgreSQL: CRUD example
– React + Spring Boot + MongoDB: CRUD example
– React + Node.js + Express + MySQL: CRUD example
– React + Node.js + Express + PostgreSQL example
React Redux + Node.js + Express + MySQL: CRUD example
– React + Node.js + Express + MongoDB example
– React + Django + Rest Framework example
Source Code
You can find the complete source code for this tutorial on Github.
Hello
Thank you for this great Tutorial.
How is it possible to alert the user when the active “Tutorial” Element get changed?
And how to build a button for the User to refetch de Data on active Element?
This would be very helpful.
Thank you and regards
allphii