Build a Firestore CRUD App with React 19 and Firebase 10+

by Didin J. on Jul 16, 2025 Build a Firestore CRUD App with React 19 and Firebase 10+

Learn how to build a modern Firestore CRUD web app using React 19.1.0 and Firebase v10+. Includes React Hooks, Router v6, and Firebase modular API.

React and Firebase make a powerful combo for building real-time web apps. In this tutorial, we’ll walk through building a Firestore CRUD app using the latest versions:

  • ⚛️ React 19.1.0 with Hooks & functional components

  • 🔥 Firebase v10+ using modular imports

  • 🛣️ React Router v6

  • ✅ No class components, no deprecated APIs

The result will be a clean and modern CRUD web application.

Prerequisites


1. Set Up Your React Project

Create the app using Vite (or CRA):

npm create vite@latest react-firebase-crud --template react
cd react-firebase-crud
npm install

Then install dependencies:

npm install firebase react-router-dom@6


2. Firebase Setup

2.1 Create Firebase Project

  1. Go to console.firebase.google.com

  2. Create a project, then:

    • Add a Web app

    • Copy the Firebase config

    • Enable Firestore in test mode

2.2 Create firebase.ts

// src/firebase.ts
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT.appspot.com",
  messagingSenderId: "SENDER_ID",
  appId: "APP_ID",
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);

3. App Structure

src/
├── components/
│   ├── BoardList.tsx
│   ├── BoardAdd.tsx
│   ├── BoardEdit.tsx
│   └── BoardShow.tsx
├── types/
│   └── Board.ts
├── firebase.ts
├── App.tsx
└── main.tsx

Add those directories and files.

mkdir src/components
touch src/components/BoardList.tsx
touch src/components/BoardAdd.tsx
touch src/components/BoardEdit.tsx
touch src/components/BoardShow.tsx
mkdir src/types
touch src/types/Board.ts


4. Routing Setup

main.tsx

import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { BrowserRouter } from "react-router-dom";

createRoot(document.getElementById("root")!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

App.tsx

import { Routes, Route, Link } from 'react-router-dom';
import BoardList from './components/BoardList';
import BoardAdd from './components/BoardAdd';
import BoardEdit from './components/BoardEdit';
import BoardShow from './components/BoardShow';

function App() {
  return (
    <div className="container">
      <h1>React Firebase Firestore CRUD</h1>
      <nav>
        <Link to="/">Boards</Link> | <Link to="/add">Add</Link>
      </nav>
      <Routes>
        <Route path="/" element={<BoardList />} />
        <Route path="/add" element={<BoardAdd />} />
        <Route path="/edit/:id" element={<BoardEdit />} />
        <Route path="/show/:id" element={<BoardShow />} />
      </Routes>
    </div>
  );
}

export default App;


5. CRUD Components

5.1 src/types/Board.ts

// src/types/Board.ts
export interface Board {
  id?: string;
  title: string;
  description: string;
}

5.2 src/components/BoardList.tsx

import { useEffect, useState } from "react";
import { db } from "../firebase";
import { collection, getDocs, deleteDoc, doc } from "firebase/firestore";
import { Link } from "react-router-dom";
import type { Board } from "../types/Board";

function BoardList() {
  const [boards, setBoards] = useState<Board[]>([]);

  useEffect(() => {
    const fetchBoards = async () => {
      const snapshot = await getDocs(collection(db, "boards"));
      setBoards(
        snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() } as Board))
      );
    };
    fetchBoards();
  }, []);

  const deleteBoard = async (id: string) => {
    await deleteDoc(doc(db, "boards", id));
    setBoards(boards.filter((board) => board.id !== id));
  };

  return (
    <div>
      <h2>Board List</h2>
      <ul>
        {boards.map((board) => (
          <li key={board.id}>
            <Link to={`/show/${board.id}`}>{board.title}</Link>{" "}
            <Link to={`/edit/${board.id}`}>Edit</Link>{" "}
            <button onClick={() => deleteBoard(board.id!)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default BoardList;

5.2 BoardAdd.jsx

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { db } from "../firebase";
import { collection, addDoc } from "firebase/firestore";

function BoardAdd() {
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const navigate = useNavigate();

  const onSubmit = async (e: any) => {
    e.preventDefault();
    await addDoc(collection(db, "boards"), { title, description });
    navigate("/");
  };

  return (
    <form onSubmit={onSubmit}>
      <h2>Add Board</h2>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Title"
        required
      />
      <textarea
        value={description}
        onChange={(e) => setDescription(e.target.value)}
        placeholder="Description"
        required
      />
      <button type="submit">Save</button>
    </form>
  );
}

export default BoardAdd;

5.3 BoardEdit.jsx

import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { db } from "../firebase";
import { doc, getDoc, updateDoc } from "firebase/firestore";
import type { Board } from "../types/Board";

function BoardEdit() {
  const [board, setBoard] = useState<Board | undefined>();
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();

  useEffect(() => {
    if (!id) return;

    const loadBoard = async () => {
      const snap = await getDoc(doc(db, "boards", id));
      setBoard(snap.data() as Board);
    };
    loadBoard();
  }, [id]);

  const onSubmit = async (e: any) => {
    if (!id || !board) return;

    e.preventDefault();
    await updateDoc(doc(db, "boards", id), { ...board });
    navigate("/");
  };

  return (
    <form onSubmit={onSubmit}>
      <h2>Edit Board</h2>
      <input
        value={board!.title}
        onChange={(e) => {
          if (!board) return;
          setBoard({ ...board, title: e.target.value });
        }}
        required
      />
      <textarea
        value={board!.description}
        onChange={(e) => {
          if (!board) return;
          setBoard({ ...board, description: e.target.value });
        }}
        required
      />
      <button type="submit">Update</button>
    </form>
  );
}

export default BoardEdit;

5.4 BoardShow.jsx

import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { db } from "../firebase";
import { doc, getDoc } from "firebase/firestore";
import type { Board } from "../types/Board";

function BoardShow() {
  const [board, setBoard] = useState<Board | undefined>();
  const { id } = useParams();

  useEffect(() => {
    if (!id) return;

    const fetchBoard = async () => {
      const snap = await getDoc(doc(db, "boards", id));
      setBoard(snap.data() as Board);
    };
    fetchBoard();
  }, [id]);

  return board ? (
    <div>
      <h2>{board.title}</h2>
      <p>{board.description}</p>
    </div>
  ) : (
    <p>Loading...</p>
  );
}

export default BoardShow;


Test It Out

npm run dev
  • Navigate to /add, create a board

  • See it listed on /

  • Click to view, edit, or delete

Build a Firestore CRUD App with React 19 and Firebase 10+ - Board List

Build a Firestore CRUD App with React 19 and Firebase 10+ - Board Details

Build a Firestore CRUD App with React 19 and Firebase 10+ - Add oard


Conclusion

You’ve now built a full-featured CRUD Firestore App using:

  • ✅ React 19.1.0

  • ✅ Firebase 10+ modular SDK

  • ✅ React Router 6

  • ✅ Functional components and hooks

This codebase is cleaner, modern, and future-ready.

You can find the full working source code on our GitHub.

That's just the basics. If you need more deep learning about MERN Stack, React.js, or React Native, you can take the following cheap course:

Thanks!