Firestore with React: Build Realtime CRUD Apps in 2025

Author

Kritim Yantra

Jun 22, 2025

Firestore with React: Build Realtime CRUD Apps in 2025

Ever built a to-do app that doesn’t update instantly when you add a task? Feels like sending a letter 🏷️ instead of a text 📲, right?

With Firestore, your React apps can sync data in real-time—no refresh needed! By the end of this guide, you’ll build a real-time CRUD app with:

Create, Read, Update, Delete operations
Live data sync (changes appear instantly)
Optimized queries (fetch only what you need)
Security rules (keep data safe)

Let’s turn your app into a real-time powerhouse!


🛠️ What You’ll Need

Before we start:

  • A React project (create one with npx create-react-app firestore-crud)
  • Firebase setup (from our last guide)
  • Firestore enabled in Firebase Console

🔥 Step 1: Setting Up Firestore

Firestore is like Google Sheets on steroids 📊—but for apps.

1. Enable Firestore in Firebase

  • Go to Firebase Console
  • Select Firestore Database → Create Database
  • Start in test mode (we’ll secure it later)

2. Install Firebase in React

Run:

npm install firebase @react-firebase/firestore

3. Initialize Firestore

Update firebase.js:

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

const firebaseConfig = { ... }; // Your config from last guide

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app); // Firestore instance

Now you’re ready to read/write data!


📝 Step 2: CRUD Operations in Firestore

1. Add Data (Create)

import { db } from "./firebase";
import { collection, addDoc } from "firebase/firestore";

const addTodo = async (text) => {
  try {
    const docRef = await addDoc(collection(db, "todos"), {
      text: text,
      completed: false,
      createdAt: new Date(),
    });
    console.log("Todo added with ID:", docRef.id);
  } catch (error) {
    console.error("Error adding todo:", error);
  }
};

2. Read Data (Read)

Fetch todos once:

import { getDocs, collection } from "firebase/firestore";

const fetchTodos = async () => {
  const querySnapshot = await getDocs(collection(db, "todos"));
  querySnapshot.forEach((doc) => {
    console.log(doc.id, "=>", doc.data());
  });
};

Real-time updates (🔥 game-changer!):

import { onSnapshot } from "firebase/firestore";

useEffect(() => {
  const unsubscribe = onSnapshot(collection(db, "todos"), (snapshot) => {
    const todos = [];
    snapshot.forEach((doc) => {
      todos.push({ id: doc.id, ...doc.data() });
    });
    setTodos(todos); // Updates state automatically
  });

  return unsubscribe; // Cleanup on unmount
}, []);

3. Update Data (Update)

import { doc, updateDoc } from "firebase/firestore";

const toggleTodo = async (id, completed) => {
  await updateDoc(doc(db, "todos", id), {
    completed: !completed,
  });
};

4. Delete Data (Delete)

import { deleteDoc, doc } from "firebase/firestore";

const deleteTodo = async (id) => {
  await deleteDoc(doc(db, "todos", id));
};

Step 3: Optimizing Firestore Queries

Filter Data (e.g., show only incomplete todos)

import { query, where } from "firebase/firestore";

const q = query(
  collection(db, "todos"),
  where("completed", "==", false)
);

Order Data (e.g., newest first)

import { orderBy } from "firebase/firestore";

const q = query(
  collection(db, "todos"),
  orderBy("createdAt", "desc")
);

Pagination (fetch in chunks)

import { limit, startAfter } from "firebase/firestore";

const firstBatch = query(collection(db, "todos"), limit(10));
// Fetch next 10 after last doc
const nextBatch = query(
  collection(db, "todos"),
  startAfter(lastVisibleDoc),
  limit(10)
);

🔒 Step 4: Securing Firestore

Test mode is insecure! Add rules in Firebase Console → Firestore → Rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{todoId} {
      allow read, write: if request.auth != null;
      // Only the owner can edit their todos
      allow create: if request.auth.uid == request.resource.data.userId;
    }
  }
}

🎨 Step 5: Building the UI

Here’s a simple React Todo App using Firestore:

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");

  // Real-time listener (from Step 2)
  useEffect(() => { ... }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    addTodo(input); // From Step 2
    setInput("");
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a todo"
        />
      </form>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id, todo.completed)}
            />
            <span>{todo.text}</span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

🚀 Key Takeaways

Firestore = Real-time NoSQL database for apps
CRUD operations are simple with addDoc, updateDoc, deleteDoc
Live updates with onSnapshot (no refresh needed!)
Secure data with Firebase Rules

Stuck? Ask below! 👇

#HappyCoding 💻🔥

Ajay Yadav

Ajay Yadav

Senior Full-Stack Engineer

7 + Years Experience

Transforming Ideas Into Digital Solutions

I architect and build high-performance web applications with modern tech:

Laravel PHP 8+ Vue.js React.js Flask Python MySQL

Response time: under 24 hours • 100% confidential

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts