In this tutorial, I will show you step by step to build a React Typescript Firebase CRUD example with Realtime Database.
Related Posts:
– React Typescript Firestore example: Build a CRUD App
– React Typescript example Project with Axios and Web API
Contents
- Firebase Typescript with React Overview
- CRUD Operations using firebase Reference
- Technology
- Setup the Firebase Project
- Setup React Typescript Firebase Project
- Import Bootstrap to Firebase Typescript Project
- Add React Router to Firebase Typescript Project
- Add Navbar to Firebase Typescript Project
- Define Data Type
- Integrate Firebase into Firebase Typescript App
- Create Data Service
- Component for creating Object
- Component for List of Objects
- Component for Object details
- Add CSS style for React Typescript Components
- Run & Check
- Conclusion
- Further Reading
- Source Code
Firebase Typescript with React Overview
We’re gonna build an React Typescript Firebase CRUD Application 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:
CRUD Operations using firebase Reference
We’re gonna use instance of firebase.database.Reference to read/write data from the Firebase database.
let tutorialsRef = firebase.database().ref("/tutorials");
– Read list once using once()
:
tutorialsRef.once('value', function(snapshot: any) {
let tutorials = new Array<ITutorialData>();
snapshot.forEach(function(childSnapshot: any) {
let key = childSnapshot.key;
let 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: any) {
// data.key, data.val().title, data.val().description
});
tutorialsRef.on('child_changed', function(data: any) {
// data.key, data.val().title, data.val().description
});
tutorialsRef.on('child_removed', function(data: any) {
// data.key, data.val().title, data.val().description
});
– Listening for all value events on a List reference
let onDataChange = tutorialsRef.on('value', function(snapshot: any) {
snapshot.forEach(function(childSnapshot: any) {
let childKey = childSnapshot.key;
let 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 17/16
- firebase 8
- typescript 4.3.5
- react-router-dom 5
- bootstrap 4.6.0
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 Typescript Firebase Project
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-typescript-firebase --template typescript
After the process is done. We create additional folders and files like the following tree:
public
src
components
add-tutorial.component.tsx
tutorial.component.tsx
tutorials-list.component.tsx
services
tutorial.service.ts
types
tutorial.type.ts
App.css
App.tsx
firebase.ts
index.tsx
package.json
Let me explain it briefly.
– firebase.ts
configures information to connect with Firebase Project and export Firebase Database service.
– services/tutorial.service.ts
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 itemtutorials-list
contains list of items, parent oftutorial
tutorial
shows item details
– App.tsx
contains Browser Router view and navigation bar.
Import Bootstrap to Firebase Typescript Project
Run command:
– yarn add [email protected]
– or npm install [email protected]
Open src/App.tsx 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 Firebase Typescript Project
When using Typescript with React, we don’t use Proptypes. Typescript is stronger than Propstypes.
npm
has many dependencies with prefix @types/{name}
such as @types/lodash
, @types/react
… which is easy to install and use. For this project, we use @types/react-router-dom
.
– Run the command:
yarn add react-router-dom @types/react-router-dom
or npm install react-router-dom @types/react-router-dom
– Open src/index.tsx and wrap App
component by BrowserRouter
object.
import ReactDOM from 'react-dom';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();
Open src/App.tsx, 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.
import { 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">
<Link to={"/tutorials"} className="navbar-brand">
bezKoder
</Link>
<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 Typescript Firebase example</h2>
<Switch>
<Route exact path={["/", "/tutorials"]} component={TutorialsList} />
<Route exact path="/add" component={AddTutorial} />
</Switch>
</div>
</div>
);
}
}
export default App;
Define Data Type
Now we need to define the data type for Tutorial
. Create and export ITutorialData
interface in types/tutorial.type.ts.
export default interface ITutorialData {
key?: string | null,
title: string,
description: string,
published?: boolean,
}
Integrate Firebase into Firebase Typescript App
First run the command:
– yarn add [email protected]
– or npm install [email protected]
Open src/firebase.ts, import firebase
library and add configuration that we have saved when Popup window was shown:
import firebase from "firebase/app";
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.ts
import firebase from "../firebase";
import ITutorialData from "../types/tutorial.type"
const db = firebase.ref("/tutorials");
class TutorialDataService {
getAll() {
return db;
}
create(tutorial: ITutorialData) {
return db.push(tutorial);
}
update(key: string, value: any) {
return db.child(key).update(value);
}
delete(key: string) {
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.tsx
import { Component, ChangeEvent } from "react";
import TutorialDataService from "../services/tutorial.service";
import ITutorialData from '../types/tutorial.type';
type Props = {};
type State = ITutorialData & {
submitted: boolean
};
export default class AddTutorial extends Component<Props, State> {
constructor(props: 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: ChangeEvent<HTMLInputElement>) {
this.setState({
title: e.target.value,
});
}
onChangeDescription(e: ChangeEvent<HTMLInputElement>) {
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: Error) => {
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<Props, State> {
// ...
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.
So we will have following state:
tutorials
currentTutorial
andcurrentIndex
We also need to use 2 TutorialDataService
methods:
getAll()
deleteAll()
components/tutorials-list.component.tsx
import { Component } from "react";
import TutorialDataService from "../services/tutorial.service";
import Tutorial from "./tutorial.component";
import ITutorialData from '../types/tutorial.type';
type Props = {};
type State = {
tutorials: Array<ITutorialData>,
currentTutorial: ITutorialData | null,
currentIndex: number
};
export default class TutorialsList extends Component<Props, State> {
constructor(props: 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: any) {
let tutorials = new Array<ITutorialData>();
items.forEach((item: any) => {
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: ITutorialData, index: number) {
this.setState({
currentTutorial: tutorial,
currentIndex: index,
});
}
removeAllTutorials() {
TutorialDataService.deleteAll()
.then(() => {
this.refreshList();
})
.catch((e: Error) => {
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<Props, State> {
// ...
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.tsx
import { Component, ChangeEvent } from "react";
import TutorialDataService from "../services/tutorial.service";
import ITutorialData from "../types/tutorial.type";
type Props = {
tutorial: ITutorialData,
refreshList: Function
};
type State = {
currentTutorial: ITutorialData;
message: string;
}
export default class Tutorial extends Component<Props, State> {
constructor(props: 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: Props, prevState: State) {
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: ChangeEvent<HTMLInputElement>) {
const title = e.target.value;
this.setState(function (prevState: State) {
return {
currentTutorial: {
...prevState.currentTutorial,
title: title,
},
};
});
}
onChangeDescription(e: ChangeEvent<HTMLInputElement>) {
const description = e.target.value;
this.setState((prevState) => ({
currentTutorial: {
...prevState.currentTutorial,
description: description,
},
}));
}
updatePublished(status: boolean) {
if (this.state.currentTutorial.key) {
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: Error) => {
console.log(e);
});
}
}
updateTutorial() {
if (this.state.currentTutorial.key) {
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: Error) => {
console.log(e);
});
}
}
deleteTutorial() {
if (this.state.currentTutorial.key) {
TutorialDataService.delete(this.state.currentTutorial.key)
.then(() => {
this.props.refreshList();
})
.catch((e: Error) => {
console.log(e);
});
}
}
render() { ... }
}
And this is the code for render()
method:
export default class Tutorial extends Component<Props, State> {
// ...
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 Typescript 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: yarn start
or npm start
.
Compiled successfully!
You can now view react-typescript-firebase 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 Typescript Firebase CRUD Application successfully working with Realtime Database using firebase
library. Now we can display, modify, delete object and list at ease.
You can also find how to create React HTTP Client for working with Restful API in:
React Typescript example Project with Axios and Web API
Or Cloud Firestore for serverless:
React Typescript Firestore example: Build a CRUD App
Happy learning, see you again!
Further Reading
Fullstack:
- React Typescript + Spring Boot + H2
- 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 + Node.js + Express + MongoDB example
- React + Django + Rest Framework example
Source Code
You can find the complete source code for this tutorial on Github.