In this tutorial, I will show you step by step to build a React Firestore CRUD App example.
Related Posts:
– React Firebase CRUD with Realtime Database
– React.js CRUD example to consume Web API
– Using Hooks instead: React Hooks Firestore example: Build a CRUD app
– Typescript version: React Typescript Firestore CRUD example | Firebase Firestore
Contents
- React Firestore CRUD Overview
- CRUD Operations using Firestore CollectionReference
- Technology
- Setup the Firebase Project
- Setup React.js Project
- Import Bootstrap to React Firestore CRUD App
- Add React Router to React Firestore CRUD App
- Add Navbar to React Firestore CRUD App
- Integrate Firebase into React App
- Create Data Service
- Component for creating Documents
- Component for List of Documents
- Component for Document details
- Add CSS style for React Components
- Run & Check
- Conclusion
- Further Reading
- Source Code
React Firestore CRUD Overview
We’re gonna build an React Firestore App using firebase library in which:
- Each Tutorial has id, title, description, published status.
- We can create, retrieve, update, delete Tutorials (CRUD operations) from Firebase Cloud Firestore
Here are the screenshots:
– Create a new Tutorial:

Cloud Firestore after the Create Operations:

If you want to implement Form Validation, please visit:
React Form Validation example
– Retrieve all Tutorials, the details will show when clicking on any Tutorial:

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

– Update the Tutorial details with Update button:

– Delete the Tutorial using Delete button:

CRUD Operations using Firestore CollectionReference
We’re gonna use instance of firebase.firestore.CollectionReference to read/write data from the Firestore.
var tutorialsRef = firebase.firestore().collection("/tutorials"); – Read collection once using get():
tutorialsRef.get().then(function(snapshot) { vat tutorials = []; snapshot.forEach(function(childSnapshot) { var id = childSnapshot.id; var data = childSnapshot.val(); // ... tutorials.push({ id: id, title: data.title, description: data.description}); }); }); – Read collection with listening to the data changes using onSnapshot():
tutorialsRef.onSnapshot(function(snapshot) { snapshot.docChanges().forEach(function(change) { if (change.type === "added") { console.log("New tutorial: ", change.doc.data()); } if (change.type === "modified") { console.log("Modified tutorial: ", change.doc.data()); } if (change.type === "removed") { console.log("Removed tutorial: ", change.doc.data()); } }); }); – Listening for all value change events on a collection reference
tutorialsRef.onSnapshot(function(snapshot) { snapshot.forEach(function(childSnapshot) { var id = childSnapshot.id; var childData = childSnapshot.val(); // ... }); }); – Detach the listener to stop using bandwidth to receive updates:
var unsubscribe = tutorialsRef.onSnapshot(function(snapshot) { // ... }); // Stop listening to changes unsubscribe(); – Create a new document in collection using add():
tutorialsRef.add({ title: "bezkoder Tut#1", description: "Helpful tutorial" }) .then(function(docRef) { console.log("Tutorial created with ID: ", docRef.id); }) .catch(function(error) { console.error("Error adding Tutorial: ", error); });; – Update document by id in collection:
+ destructive update using set(): delete everything currently in place, then save the new value
tutorialsRef.doc(id).set({ title: 'zkoder Tut#1', description: 'Tut#1 Description' }); + non-destructive update using update(): only updates the specified values
tutorialsRef.doc(id).update({ title: 'zkoder new Tut#1' }); – Delete a document by id in collection:
tutorialsRef.doc(id).delete(); – Delete entire collection: Deleting Firestore collections from a Web client is not recommended.
You can find the solution here.
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:

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, a window will be shown:

Set the nickname and choose Register App for next step.

Copy the script for later use.
Choose Cloud Firestore on the left (list of Firebase features) -> 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 allow read, write value to true.
Finally, we need to set Cloud Firestore Location:

Setup React.js Project
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-firestore-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 Firestore service.
– services/tutorial.service.js exports TutorialDataService that uses firebase‘s Firestore CollectionReference to interact with Firestore.
– There are 3 components that uses TutorialDataService:
add-tutorialfor creating new itemtutorials-listcontains list of items, parent oftutorialtutorialshows item details
– App.js contains Browser Router view and navigation bar.
Import Bootstrap to React Firestore 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 Firestore 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 Component.
There are 2 main routes:
/addforadd-tutorialcomponent/tutorialsfortutorials-listcomponent
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 Firestore 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/firestore"; 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.firestore(); Don’t forget to export firebase.firestore.Firestore service with firebase.firestore().
Create Data Service
This service will use Firestore service exported above to interact with Firebase Cloud Firestore. It contains necessary functions for CRUD operations.
services/tutorial.service.js
import firebase from "../firebase"; const db = firebase.collection("/tutorials"); class TutorialDataService { getAll() { return db; } create(tutorial) { return db.add(tutorial); } update(id, value) { return db.doc(id).update(value); } delete(id) { return db.doc(id).delete(); } } export default new TutorialDataService(); Component for creating Documents
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 Documents
This component 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:
tutorialscurrentTutorialandcurrentIndex
We also need to use TutorialDataService‘s getAll() method with .orderBy("title", "asc") for sort the list by title field in ascending order.
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.onDataChange = this.onDataChange.bind(this); this.state = { tutorials: [], currentTutorial: null, currentIndex: -1, }; this.unsubscribe = undefined; } componentDidMount() { this.unsubscribe = TutorialDataService.getAll().orderBy("title", "asc").onSnapshot(this.onDataChange); } componentWillUnmount() { this.unsubscribe(); } onDataChange(items) { let tutorials = []; items.forEach((item) => { let id = item.id; let data = item.data(); tutorials.push({ id: id, 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, }); } 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 id and other fields of each item. This id 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> </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 Document 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.js
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: { id: null, title: "", description: "", published: false, }, message: "", }; } static getDerivedStateFromProps(nextProps, prevState) { const { tutorial } = nextProps; if (prevState.currentTutorial.id !== tutorial.id) { 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.id, { 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.id, data) .then(() => { this.setState({ message: "The tutorial was updated successfully!", }); }) .catch((e) => { console.log(e); }); } deleteTutorial() { TutorialDataService.delete(this.state.currentTutorial.id) .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-firestore-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 Firestore CRUD Application successfully working with Cloud Firestore using firebase library. Now we can display, modify, delete documents and collection at ease.
If you want to implement Form Validation, please visit:
React Form Validation 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 Firebase Realtime Database for serverless:
React Firebase CRUD with Realtime Database
Using Hooks instead: React Hooks Firestore example: Build a CRUD app
Happy learning, see you again!
Further Reading
- React Component
- firebase.firestore.Firestore
- firebase.firestore.CollectionReference
- Firestore Get Started
Fullstack:
- React + Spring Boot + MySQL/PostgreSQL: CRUD example
- React + Spring Boot + MongoDB: CRUD example
- React + Node.js + Express + MySQL: CRUD example
- React + Node.js + Express + PostgreSQL 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.
– Typescript version: React Typescript Firestore CRUD example | Firebase Firestore

good React and Firebase tutorial. thanks!
I have learned lot of things from this tutorial, thanks!
Excellent way of explaining, and pleasant tutorial. Thanks!
Your React tutorials are good for new programmers.
Hi there! I simply want to give you a huge thumbs up for your great tutorial.
Many thanks!
I appreciate you taking the time and effort to write this tutorial. Many thanks!
A lot of people will be benefited from your tutorials. Cheers!
Hello,
thank u for this tut
Could u pls answer why did u use classes? I am new to all this stuff but via internet ppl keep saying that classes are dead after Hooks API’s release
Hi, you can find tutorial that uses Hooks at:
React Hooks Firestore example: Build a CRUD app