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 💻🔥
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Flask Blog CMS with Admin Dashboard — Python Source Code
A Flask-based blogging website with a built-in admin dashboard. Manage articles, images, categories, tags and featured p...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.