Build a Real-Time Hospital Bed Tracker
Build a live hospital bed dashboard with React, Firestore, and Firebase Hosting.
Introduction
30 Second Summary
When an emergency strikes and you need a hospital bed, knowing which hospitals have availability can make all the difference. In India, where public hospitals serve millions, real-time bed tracking is a problem that cloud technology can solve.
In this project, you will build ArogyaBed India, a hospital bed availability dashboard powered by React and Firebase. You will connect it to a cloud database, add real-time data sync, and deploy it to a live public URL.
What You'll Build
Open your finished dashboard, change a hospital's bed count in the Firebase Console, and watch the card on screen update the same second, ready to share via a live URL with your college faculty.
By the end of this project, you'll have:
- A live dashboard displaying hospital bed availability across Indian states, with color-coded indicators showing available (green), limited (yellow), and full (red) capacity at a glance.
- Real-time cloud sync where you update a bed count in the Firebase Console and watch the dashboard reflect the change instantly, no page refresh needed.
- Search and state filtering to find hospitals by name or narrow results by state, all deployed to a public Firebase Hosting URL you can share with anyone.
- Secret Mission: Add Firebase Authentication so only logged-in admins can update bed counts directly through the dashboard.
Are there any prerequisites?
This project assumes basic knowledge of HTML, CSS, JavaScript, and React fundamentals. You will need a Google account for Firebase, and a code editor like VS Code or Cursor with Node.js installed on your machine.
Before We Start
Before you dive into code, take a moment to lock in what you are building and why it matters for your cloud computing skills.
Set Up Your Development Environment
Your hospital bed tracker needs two things: a React frontend to display the dashboard, and a Firebase backend to store and sync hospital data in the cloud.
In this step, you will scaffold a React project with Vite, create a Firebase project with a cloud database, and wire them together so your app can talk to the cloud.
In this step, get ready to:
- Scaffold a React project with Vite and install the Firebase SDK.
- Create a Firebase project with a Firestore database.
- Connect the React app to Firebase with a config file.
Scaffold the React project with Vite and install the Firebase SDK
Vite is a modern build tool that scaffolds React projects fast. You will use it to generate the project skeleton and then add the Firebase SDK on top.
- Confirm your Node.js version supports Vite by running this command:
node --version
You should see version 20.19 or higher (for example, v22.14.0).
✔️ I see version 20.19 or higher
Your Node.js version is compatible with Vite. Continue to the next bullet.
ⓧ I see a version lower than 20.19
Vite requires Node.js version 20.19+ or 22.12+. Visit the official Node.js downloads page at nodejs.org and install the latest LTS version.
After installing, close and reopen your terminal. Run node --version again to confirm the update.
Help me upgrade Node.js
ⓧ Command not found
Node.js is not installed or not on your system path. Visit nodejs.org and download the latest LTS version for your operating system.
After installing, close and reopen your terminal. Run node --version again to confirm Node.js is available.
Help me install Node.js
- Move to your Desktop by running this command:
cd ~/Desktop
What will this command do?
The next command downloads the create-vite scaffolding tool and generates a new React project in a folder called arogyabed-india. The --template react flag tells Vite to use the React starter template.
You may see a prompt asking to install the create-vite package. Enter y to proceed. The command may also ask whether to install dependencies and start the dev server immediately. Enter No if it appears.
- Create the React project by running this command:
npm create vite@latest arogyabed-india -- --template react
- Confirm the project was created by running:
ls arogyabed-india
You should see files like index.html, package.json, src/, and vite.config.js listed in the output.
- Navigate into the project and install its dependencies by running these commands:
cd arogyabed-india
npm install
- Install the Firebase SDK by running:
npm install firebase@12.17.1
What is the Firebase SDK?
The Firebase SDK is a JavaScript library that lets your React app communicate with Firebase services like Firestore (the cloud database you will use). Pinning the version to 12.17.1 ensures everyone following this project gets the same behavior.
Before you start the development server, what do you think you will see when you open http://localhost:5173 in the browser?
- Start the Vite development server by running:
npm run dev
What does this command do?
This starts a local development server that serves your React app. It takes over the current terminal window, so leave it running.
To stop the server later, press Ctrl+C in the terminal.
- Open your browser and go to http://localhost:5173.
You should see the default Vite React starter page with a spinning React logo and a counter button.
Don't see the starter page?
Make sure the terminal shows a local URL like http://localhost:5173. If the terminal shows an error, check that you ran npm install before npm run dev.
If port 5173 is already in use, Vite picks the next available port. Check the terminal output for the actual URL.
Help me troubleshoot the Vite starter page
Create a Firebase project and Firestore database
Firebase is Google's app development platform. You will create a Firebase project to house your cloud database and, later, host your deployed app.
- Open your browser and go to console.firebase.google.com.
- Sign in with your Google account.
- Click Add project.
- Enter ArogyaBed as the project name.
- When prompted about Google Analytics, toggle it off (you do not need it for this project).
- Click Create project.
- Wait for the project to finish provisioning, then click Continue.
You should now be on the Firebase project dashboard for ArogyaBed.
What is a Firebase project?
A Firebase project is a container for all the cloud services your app uses. It holds your database, hosting configuration, authentication settings, and more. Everything lives under one project.
- In the left sidebar, click Databases & Storage.
- Click Firestore.
- Click Create database.
- For the database location, select asia-south1 (Mumbai). This is the closest region to India and gives your users the lowest latency.
- Select Start in test mode for the security rules. Test mode allows your app to read and write freely during development.
- Click Create.
You should see an empty Firestore database page with a message prompting you to add data.
What is Firestore?
Firestore is a NoSQL cloud database. Instead of tables and rows like a traditional database, Firestore stores data as documents grouped into collections. Your app reads and writes these documents over the internet.
Test mode lets any client read and write data for 30 days. This is fine for development, and the Secret Mission introduces authentication to lock it down.
- Now register a web app so Firebase gives you a config object. Click the gear icon next to Project Overview in the left sidebar.
- Click Project settings.
- Scroll down to the Your apps section.
- Click the web app icon (</>).
- Enter ArogyaBed Web as the app nickname.
- Leave the Firebase Hosting checkbox unchecked for now. You will set up hosting in a later step.
- Click Register app.
- Firebase displays a code snippet containing your firebaseConfig object. Copy the entire firebaseConfig object (the part inside the curly braces with apiKey, authDomain, etc.) and save it somewhere handy. You will need it in the next substep.
- Click Continue to console.
Keep your config values ready
Paste the firebaseConfig object into a temporary text file or note. You will paste these values into your code in the next substep.
Connect the React app to Firebase
Your React project and Firebase project exist separately right now. A config file bridges the two by telling your app which Firebase project to connect to.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code (or Cursor) and press Enter to open it.
- Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the command palette.
- Type Open Folder and select it.
- Navigate to your Desktop and select the arogyabed-india folder, then click Open.
- In the Explorer sidebar, right-click the src folder and select New File.
- Name the file firebase.js.
- Paste the following code into src/firebase.js, replacing each placeholder value with the matching value from the firebaseConfig you copied from the Firebase Console:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
// Replace each placeholder with your Firebase config values
const firebaseConfig = {
apiKey: "your-api-key-here",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.firebasestorage.app",
messagingSenderId: "your-sender-id-here",
appId: "your-app-id-here",
};
// Initialize the Firebase app with your config
const app = initializeApp(firebaseConfig);
// Create and export the Firestore database instance
export const db = getFirestore(app);
What does this code do?
The initializeApp function creates a connection to your Firebase project using the config values you pasted. Each value (apiKey, projectId, etc.) identifies your specific project in the cloud.
The getFirestore function creates a Firestore database instance and exports it as db. Every file in your app that needs to read or write cloud data will import this db variable.
- Save src/firebase.js.
- Check the terminal where npm run dev is running. It should show no errors. The browser at http://localhost:5173 should still display the Vite React starter page.
Seeing an error in the terminal?
Make sure every placeholder in firebaseConfig is replaced with a real value from the Firebase Console. A leftover placeholder like your-api-key-here will not cause a crash at this stage, but a syntax error (missing comma, extra quote) will.
Also confirm the file is saved at src/firebase.js inside the arogyabed-india folder, not at the project root.
Help me fix this terminal error
✔️ Awesome, I've got everything!
Great. Double check you have saved src/firebase.js and the Vite dev server is still running without errors.
ⓧ I'd like to double check the full code
Here is the complete src/firebase.js file. Make sure your version matches this structure, with your own Firebase config values in place of the placeholders:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: "your-api-key-here",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.firebasestorage.app",
messagingSenderId: "your-sender-id-here",
appId: "your-app-id-here",
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
Your development environment is fully wired up. You have a running React app connected to a live Firestore database in the cloud. Next up, you will build the actual dashboard with hospital cards and color-coded status indicators.
Build the Dashboard with Hardcoded Data
Your React project is running and your Firebase backend is ready. Before connecting to the cloud database, you need a visible UI that can display hospital information.
In this step, you'll build a working dashboard with hardcoded data for hospitals across India. This gives you a functional, styled interface you can see and interact with right away.
In this step, get ready to:
- Create sample hospital data for 10 Indian hospitals.
- Build a reusable HospitalCard component with color-coded status badges.
- Style the dashboard with a responsive card grid layout.
Create sample hospital data and the main dashboard layout
The Vite scaffold created a default App.jsx with demo content. You'll replace it with a dashboard header and create a data file holding information about hospitals across India.
- Replace the entire contents of src/App.jsx with the dashboard header by pasting this code:
import "./App.css";
// Main dashboard component
function App() {
return (
<div className="app">
<header className="header">
<h1>ArogyaBed India</h1>
<p>Real-time Hospital Bed Availability Tracker</p>
</header>
{/* Hospital cards will be rendered here */}
<div className="hospital-grid"></div>
</div>
);
}
export default App;
What does this code do?
- className="app" wraps the entire dashboard in a styled container.
- The header section displays the app title and subtitle.
- The hospital-grid div is an empty container where hospital cards will appear once the component is built.
- Save App.jsx.
- Check your browser at http://localhost:5173. You should see the "ArogyaBed India" heading and subtitle text on the page.
Don't see the heading?
- Make sure the dev server is still running in your terminal. If it stopped, run npm run dev from inside the arogyabed-india folder.
- Check that you replaced the entire file contents, not pasted below the existing Vite demo code.
Help me debug why my ArogyaBed India header is not showing up.
Now you need sample hospital data. You'll create a data file with 10 hospitals from across India, each with bed counts and contact information.
- Create a new folder called data inside the src folder.
- Create a new file called hospitals.js inside src/data/.
- The file is long, so you'll build it in parts. Start by pasting the first three hospitals:
// Sample hospital data across Indian states
const hospitals = [
{
name: "AIIMS Delhi",
city: "New Delhi",
state: "Delhi",
totalBeds: 200,
availableBeds: 45,
icuAvailable: 5,
contact: "011-26588500",
},
{
name: "Safdarjung Hospital",
city: "New Delhi",
state: "Delhi",
totalBeds: 180,
availableBeds: 12,
icuAvailable: 0,
contact: "011-26707437",
},
{
name: "KEM Hospital",
city: "Mumbai",
state: "Maharashtra",
totalBeds: 250,
availableBeds: 80,
icuAvailable: 10,
contact: "022-24136051",
},
What does each hospital object contain?
- name, city, and state identify the hospital and its location.
- totalBeds and availableBeds track overall capacity and current openings. The ratio between them drives the status color.
- icuAvailable shows critical care bed count. contact provides the emergency phone number.
- Add these three hospitals below the KEM Hospital entry in the same file:
{
name: "Sion Hospital",
city: "Mumbai",
state: "Maharashtra",
totalBeds: 150,
availableBeds: 5,
icuAvailable: 0,
contact: "022-24076381",
},
{
name: "Victoria Hospital",
city: "Bengaluru",
state: "Karnataka",
totalBeds: 300,
availableBeds: 120,
icuAvailable: 15,
contact: "080-26701150",
},
{
name: "Bowring Hospital",
city: "Bengaluru",
state: "Karnataka",
totalBeds: 160,
availableBeds: 30,
icuAvailable: 3,
contact: "080-25591325",
},
- Add these two hospitals below the Bowring Hospital entry:
{
name: "Rajiv Gandhi GH",
city: "Chennai",
state: "Tamil Nadu",
totalBeds: 280,
availableBeds: 0,
icuAvailable: 0,
contact: "044-25305000",
},
{
name: "Stanley Medical College",
city: "Chennai",
state: "Tamil Nadu",
totalBeds: 200,
availableBeds: 55,
icuAvailable: 8,
contact: "044-25281665",
},
- Add the final two hospitals, close the array, and export it by pasting this below the Stanley Medical College entry:
{
name: "PGIMER",
city: "Chandigarh",
state: "Chandigarh",
totalBeds: 220,
availableBeds: 40,
icuAvailable: 6,
contact: "0172-2746018",
},
{
name: "Osmania General Hospital",
city: "Hyderabad",
state: "Telangana",
totalBeds: 190,
availableBeds: 25,
icuAvailable: 2,
contact: "040-24600146",
},
];
export default hospitals;
- Save hospitals.js. Scroll through the file and confirm you see 10 hospital objects in the array, starting with AIIMS Delhi and ending with Osmania General Hospital.
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file before moving on.
ⓧ I'd like to double check the full code
The file is long, so scroll through src/data/hospitals.js and confirm you have all four sections in order:
- The const hospitals = [ declaration with AIIMS Delhi, Safdarjung Hospital, and KEM Hospital.
- Sion Hospital, Victoria Hospital, and Bowring Hospital.
- Rajiv Gandhi GH and Stanley Medical College.
- PGIMER and Osmania General Hospital, followed by ]; and export default hospitals;.
Build the HospitalCard component
Each hospital needs its own visual card on the dashboard. You'll create a component that takes a hospital object and renders its details with a color-coded availability badge.
- Create a new folder called components inside the src folder.
- Create a new file called HospitalCard.jsx inside src/components/.
- Start building the component by pasting the status helper function and the card header:
// Determine bed availability status based on the ratio
function getStatus(available, total) {
const ratio = available / total;
if (ratio > 0.3) return { label: "Available", color: "#16a34a" };
if (ratio >= 0.1) return { label: "Limited", color: "#ca8a04" };
return { label: "Full", color: "#dc2626" };
}
// Display a single hospital's information as a card
function HospitalCard({ hospital }) {
const status = getStatus(hospital.availableBeds, hospital.totalBeds);
return (
<div className="hospital-card">
<div className="card-header">
<h3>{hospital.name}</h3>
<span className="status-badge" style={{ backgroundColor: status.color }}>
{status.label}
</span>
</div>
<p className="location">
{hospital.city}, {hospital.state}
</p>
How does the status color work?
The getStatus function divides available beds by total beds to get a ratio. If more than 30% of beds are free, the badge is green ("Available"). Between 10% and 30% it turns yellow ("Limited"). Below 10% it turns red ("Full").
The style={{ backgroundColor: status.color }} applies the color directly to the badge as an inline style.
- Add the bed information section and the contact line below the location paragraph by pasting this code:
<div className="bed-info">
<div className="bed-stat">
<span className="bed-number">{hospital.availableBeds}</span>
<span className="bed-label">Available</span>
</div>
<div className="bed-stat">
<span className="bed-number">{hospital.totalBeds}</span>
<span className="bed-label">Total Beds</span>
</div>
<div className="bed-stat">
<span className="bed-number">{hospital.icuAvailable}</span>
<span className="bed-label">ICU</span>
</div>
</div>
<p className="contact">Emergency: {hospital.contact}</p>
</div>
);
}
export default HospitalCard;
What does the bed-info section show?
Three stat blocks display the key numbers side by side: available beds, total beds, and ICU beds. The contact line at the bottom shows the hospital's emergency phone number.
- Save HospitalCard.jsx.
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file before moving on.
ⓧ I'd like to double check the full code
Scroll through src/components/HospitalCard.jsx and confirm you have both sections in order:
- The getStatus function (green/yellow/red logic), followed by the HospitalCard function opening with the card header and location paragraph.
- The bed-info div with three stat blocks, the contact line, closing tags, and export default HospitalCard; at the bottom.
The component is ready. Now you need to wire it into App.jsx alongside the hospital data.
- In main.jsx, find this line near the top:
import './index.css'
- Delete this entire line. It loads Vite's default dark-theme styles that will conflict with your custom dashboard styles.
- Save main.jsx.
- Now replace the entire contents of src/App.jsx with this updated version that imports the data and renders hospital cards:
import { useState, useEffect } from "react";
import hospitals from "./data/hospitals";
import HospitalCard from "./components/HospitalCard";
import "./App.css";
// Main dashboard component
function App() {
return (
<div className="app">
<header className="header">
<h1>ArogyaBed India</h1>
<p>Real-time Hospital Bed Availability Tracker</p>
</header>
{/* Render a card for each hospital in the data */}
<div className="hospital-grid">
{hospitals.map((hospital, index) => (
<HospitalCard key={index} hospital={hospital} />
))}
</div>
</div>
);
}
export default App;
What changed in App.jsx?
- hospitals.map() loops through every hospital in the data array and renders a HospitalCard for each one.
- useState and useEffect are imported now but not used yet. You'll need them in a later step when the data comes from Firestore instead of a local file.
- Save App.jsx.
- Check your browser. You should see 10 hospital entries displayed as plain text with names, cities, bed counts, and phone numbers.
Seeing an error or blank page?
- Check the terminal for error messages. A common cause is a typo in the import paths. Make sure ./data/hospitals and ./components/HospitalCard match the folder and file names you created exactly.
- Verify that hospitals.js ends with export default hospitals; and that HospitalCard.jsx ends with export default HospitalCard;.
Help me debug why my hospital cards are not showing up.
✔️ Awesome, I've got everything!
Great. Double check that you've saved App.jsx.
ⓧ I'd like to double check the full code
Here is the full src/App.jsx at this point:
import { useState, useEffect } from "react";
import hospitals from "./data/hospitals";
import HospitalCard from "./components/HospitalCard";
import "./App.css";
function App() {
return (
<div className="app">
<header className="header">
<h1>ArogyaBed India</h1>
<p>Real-time Hospital Bed Availability Tracker</p>
</header>
<div className="hospital-grid">
{hospitals.map((hospital, index) => (
<HospitalCard key={index} hospital={hospital} />
))}
</div>
</div>
);
}
export default App;
Add basic CSS styling
The hospital data is on screen, but the layout is plain browser defaults. You'll replace the Vite demo CSS with custom styles for the card grid, status badges, and clean typography.
- Replace the entire contents of src/App.css with the dashboard reset and header styles. The file has Vite's default styles right now. Select all and delete them, then paste this code:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f0f4f8;
color: #1a202c;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
padding: 30px 0;
}
.header h1 {
font-size: 2rem;
color: #1e40af;
}
.header p {
color: #64748b;
margin-top: 8px;
}
What do these styles do?
- The * reset removes default browser margins and padding so the layout starts from a clean slate.
- .app centers the dashboard with a max width of 1200px.
- The header styles center the title and use a blue color for the app name.
- Save App.css and check your browser. The background should now be a light gray, and the "ArogyaBed India" heading should appear in blue, centered at the top.
- Add the card grid and card layout styles below the header styles in the same file:
.hospital-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.hospital-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.card-header h3 {
font-size: 1.1rem;
color: #1e293b;
}
- Save and check your browser. The hospital cards should now appear as white rounded boxes arranged in a grid.
- Add the status badge, location, and bed information styles below the card-header styles:
.status-badge {
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.location {
color: #64748b;
font-size: 0.9rem;
margin-bottom: 16px;
}
.bed-info {
display: flex;
justify-content: space-around;
padding: 12px 0;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
margin-bottom: 12px;
}
.bed-stat {
text-align: center;
}
- Save and check your browser. Each card should now show a colored status badge in the top right corner and a bordered section for bed stats.
- Add the final styles for the bed numbers, labels, and contact line below the bed-stat rule:
.bed-number {
display: block;
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
}
.bed-label {
font-size: 0.75rem;
color: #94a3b8;
text-transform: uppercase;
}
.contact {
color: #1e40af;
font-size: 0.85rem;
font-weight: 500;
}
- Save App.css.
Before you check your browser, what do you expect the finished dashboard to look like? How many cards should appear, and what colors do you think the status badges will be?
Check your browser at http://localhost:5173. You should see 10 hospital cards arranged in a responsive grid. Each card shows the hospital name, city and state, three bed count stats (available, total, ICU), and an emergency contact number. The status badges display green for hospitals like Victoria Hospital (120 of 300 beds available), yellow for Bowring Hospital (30 of 160), and red for Rajiv Gandhi GH (0 of 280).
Cards not styled correctly?
- If you see a dark background, check that you deleted the import './index.css' line from main.jsx and saved the file.
- If the cards are not in a grid, make sure the .hospital-grid CSS class in App.css matches className="hospital-grid" in App.jsx. Class names are case-sensitive.
Help me figure out why my hospital card styles are not applying.
✔️ Awesome, I've got everything!
Great. Make sure you've saved App.css.
ⓧ I'd like to double check the full code
Scroll through src/App.css and confirm you have all four sections in order:
- The reset (*), body, .app, .header, .header h1, and .header p rules.
- .hospital-grid, .hospital-card, .card-header, and .card-header h3 rules.
- .status-badge, .location, .bed-info, and .bed-stat rules.
- .bed-number, .bed-label, and .contact rules at the bottom.
Your dashboard looks great with hardcoded data. Next up, you'll move this data to the cloud with Firestore and discover why static data is not enough for a real-time tracker.
Move Data to the Cloud with Firestore
Your dashboard is displaying hospital cards with bed counts, status badges, and contact numbers. All that data lives in a JavaScript file inside your code, though.
In this step, you'll move the hospital data into Firestore so it lives in the cloud, completely separate from your app. Along the way, you'll spot an important limitation that sets up the next step.
In this step, get ready to:
- Seed your Firestore database with hospital data.
- Fetch hospital data from Firestore instead of the hardcoded file.
- Discover the limitation of one-time data fetching.
Seed the Firestore database with hospital data
Manually entering 10 hospitals into the Firebase Console would be tedious. Instead, you'll write a seed script that programmatically pushes your hardcoded data into Firestore.
- Create a new file called src/seed.js in your arogyabed-india project and paste in this code:
import { collection, addDoc } from "firebase/firestore";
import { db } from "./firebase";
import hospitals from "./data/hospitals";
async function seedDatabase() {
const hospitalsRef = collection(db, "hospitals");
for (const hospital of hospitals) {
await addDoc(hospitalsRef, hospital);
}
console.log("Database seeded successfully!");
}
export default seedDatabase;
What does this code do?
hospitalsRef points to a hospitals collection in your Firestore database. If the collection doesn't exist yet, Firestore creates it automatically.
The loop iterates over each hospital object from your hardcoded data file and writes it as a new document using addDoc. Each document gets an auto-generated unique ID.
- Save src/seed.js.
The seed script is ready but it won't run on its own. You need to call it once to populate your database.
- In src/main.jsx, find the last import line:
import App from "./App.jsx";
- Add these two temporary lines directly below it, before the createRoot call:
import seedDatabase from "./seed";
seedDatabase();
- Save src/main.jsx.
- Check the terminal where your dev server is running. You should see Database seeded successfully! in the browser console (open it with Cmd+Option+J on macOS or Ctrl+Shift+J on Windows).
Why the browser console?
The console.log in your seed script runs inside the browser, not in the terminal. Vite bundles and serves your code as a browser application, so all JavaScript output appears in the browser's developer console.
Now confirm the data actually arrived in Firestore.
- Switch to the Firebase Console in your browser.
- Navigate to Databases & Storage > Firestore > Data tab.
- Click on the hospitals collection. You should see 10 documents, each with a hospital's data.
Don't see the hospitals collection?
- Make sure you saved both src/seed.js and src/main.jsx. The dev server only re-runs when files are saved.
- Check that your Firebase config values in src/firebase.js match your actual project. Incorrect values silently fail.
- Open the browser console and look for error messages. A permissions error means your Firestore security rules may not be in test mode.
Why isn't my seed script writing data to Firestore?
The seed script has done its job. You only need to run it once, so remove the temporary lines now.
- In src/main.jsx, find these two lines you just added:
import seedDatabase from "./seed";
seedDatabase();
- Delete both lines so src/main.jsx is back to its original state.
- Save src/main.jsx.
Fetch data from Firestore using getDocs
Your Firestore database now has 10 hospital documents. The dashboard is still reading from the hardcoded file, though. Time to rewire it to pull data from the cloud.
- In src/App.jsx, select all the existing code (Cmd+A on macOS or Ctrl+A on Windows) and replace it with the following imports and data-fetching logic:
import { useState, useEffect } from "react";
import { collection, getDocs } from "firebase/firestore";
import { db } from "./firebase";
import HospitalCard from "./components/HospitalCard";
import "./App.css";
function App() {
// Store hospital data fetched from Firestore
const [hospitals, setHospitals] = useState([]);
useEffect(() => {
async function fetchHospitals() {
// Fetch all documents from the hospitals collection
const snapshot = await getDocs(collection(db, "hospitals"));
const hospitalData = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setHospitals(hospitalData);
}
fetchHospitals();
}, []);
What does this code do?
- getDocs fetches every document from the hospitals collection as a one-time read. It returns a snapshot of the data at that moment.
- snapshot.docs.map converts each Firestore document into a plain JavaScript object, combining the auto-generated doc.id with the document's field data.
- The empty [] at the end of useEffect means this fetch runs once when the component first loads, not on every re-render.
- Right below the useEffect block, add the component's return statement to complete the file:
return (
<div className="app">
<header className="header">
<h1>ArogyaBed India</h1>
<p>Real-time Hospital Bed Availability Tracker</p>
</header>
<div className="hospital-grid">
{hospitals.map((hospital) => (
<HospitalCard key={hospital.id} hospital={hospital} />
))}
</div>
</div>
);
}
export default App;
What changed in the JSX?
The JSX looks almost identical to before. The key difference is that hospitals now comes from React state (populated by Firestore) instead of a hardcoded import. Each hospital also uses hospital.id (the Firestore document ID) as the React key instead of the array index.
- Save src/App.jsx.
- Switch back to your browser. The dashboard should still display the same hospital cards with their bed counts and status badges.
The dashboard looks the same as before. The data is now flowing from your Firestore cloud database instead of a local file.
Seeing a blank page or no hospital cards?
- Open the browser console and check for errors. A missing import or typo in the Firestore path will show up here.
- Make sure the collection name in your code ("hospitals") matches exactly what you see in the Firebase Console. Collection names are case-sensitive.
- Verify that you removed the old import hospitals from "./data/hospitals" line. Having both the old import and the new Firestore fetch can cause conflicts.
My dashboard is blank after switching to Firestore data fetching. Help me debug.
✔️ Awesome, I've got everything!
Great. Double check you've saved src/App.jsx and that your browser shows the hospital cards.
ⓧ I'd like to double check the full code
Scroll up and confirm your src/App.jsx has all of the following sections in order:
- Five import lines: useState and useEffect from React, collection and getDocs from firebase/firestore, db from ./firebase, HospitalCard, and App.css.
- A useState call initializing hospitals to an empty array.
- A useEffect with an async fetchHospitals function that calls getDocs and sets state.
- The return JSX mapping hospitals with key={hospital.id}.
Test the limitation: no real-time updates
Your dashboard is pulling data from the cloud. But here is the critical question: what happens if someone changes the data in Firestore after your app has already loaded?
Before you test, think about this: if you change a hospital's bed count in the Firebase Console right now, do you think the dashboard will update automatically?
- Switch to the Firebase Console.
- Navigate to Firestore > Data tab and click on the hospitals collection.
- Click on any hospital document (for example, the one with name set to AIIMS Delhi).
- Click on the availableBeds field value and change it to 100.
- Switch back to your dashboard in the browser. Do NOT refresh the page.
The dashboard still shows the old bed count. The number did not change.
- Now refresh the page manually (Cmd+R on macOS or F5 on Windows).
After refreshing, the dashboard shows the updated bed count of 100. The status badge color may have changed too, depending on the new ratio.
Why didn't the dashboard update automatically?
getDocs is a one-time fetch. It reads the data once when the component first mounts, then stops listening. If someone updates the database after that initial read, your app has no way of knowing.
For a hospital bed tracker, this is a serious gap. Bed availability changes constantly, and a stale dashboard could send patients to a hospital that is already full. In the next step, you'll fix this with real-time listeners that push changes to your app the instant they happen.
Your dashboard is powered by a cloud database, and you've spotted the key limitation of one-time fetching. Next up, you'll replace getDocs with a real-time listener that pushes updates to your app the instant the data changes.
Add Real-Time Sync with Firestore Listeners
Your dashboard now pulls hospital data from Firestore in the cloud. However, you noticed a problem at the end of the last step: when you change data in the Firebase Console, the dashboard stays frozen until you manually refresh the page.
A one-time fetch is fine for a static report, but a hospital bed tracker needs to reflect changes the instant they happen. In this step, you will replace the one-time fetch with Firestore's onSnapshot listener, which keeps a live connection to the database and pushes updates to your app automatically.
In this step, get ready to:
- Replace the one-time Firestore fetch with a real-time listener.
- Test real-time sync by editing data in the Firebase Console and watching the dashboard update instantly.
Replace getDocs with onSnapshot for real-time updates
Right now, src/App.jsx uses getDocs to fetch hospital data once when the component loads. You need to swap it with onSnapshot, which opens a persistent connection and fires a callback every time the data changes.
What is onSnapshot?
getDocs reads data once and stops. It is like checking a notice board, walking away, and never coming back.
onSnapshot sets up a permanent listener. Firestore pushes every change to your app the instant it happens, with no polling and no manual refresh. This is real-time data synchronization, one of the core patterns in cloud computing.
- In src/App.jsx, find the import line near the top of the file:
import { collection, getDocs } from "firebase/firestore";
- Replace getDocs with onSnapshot, so the line now looks like this:
import { collection, onSnapshot } from "firebase/firestore";
- Now find the entire useEffect block further down in the same file:
useEffect(() => {
async function fetchHospitals() {
const snapshot = await getDocs(collection(db, "hospitals"));
const hospitalData = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setHospitals(hospitalData);
}
fetchHospitals();
}, []);
- Replace that entire useEffect block with the following code:
useEffect(() => {
// Subscribe to real-time updates from the hospitals collection
const unsubscribe = onSnapshot(
collection(db, "hospitals"),
(snapshot) => {
const hospitalData = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setHospitals(hospitalData);
}
);
// Stop listening when the component unmounts
return () => unsubscribe();
}, []);
What does this code do?
- onSnapshot(collection(db, "hospitals"), callback) tells Firestore to watch the entire hospitals collection. Every time a document is added, changed, or removed, Firestore fires the callback with a fresh snapshot.
- const unsubscribe = ... stores the function that cancels the listener. Without it, the listener would keep running even after the component is removed from the page.
- return () => unsubscribe() is React's cleanup mechanism. When the component unmounts, React calls this function to disconnect the listener.
- Save src/App.jsx.
- Switch back to your browser at http://localhost:5173. The dashboard should still display all 10 hospital cards, exactly as before.
Dashboard showing no hospitals?
- Check that you replaced getDocs with onSnapshot in both the import line AND the useEffect body. Missing either one causes an error.
- Open your browser's developer console (press F12) and look for red error messages. A message about getDocs not being defined means the import line was not updated.
- Make sure the return () => unsubscribe(); line is inside the useEffect arrow function, not outside of it.
Still stuck? Ask for help.
✔️ Awesome, I've got everything!
Great. Double check that you have saved src/App.jsx and that the dashboard still loads all 10 hospital cards.
ⓧ I'd like to double check the full code
Scroll up and confirm these two changes are present in src/App.jsx, in order:
- The import line reads import { collection, onSnapshot } from "firebase/firestore" (not getDocs).
- The useEffect body calls onSnapshot(collection(db, "hospitals"), (snapshot) => { ... }) and stores the result in unsubscribe.
- The useEffect returns () => unsubscribe() for cleanup.
Everything else in the file (the return JSX, imports for HospitalCard and App.css) stays unchanged from the previous step.
Test real-time sync across the Firebase Console and the app
The code change is small, but the behavior change is dramatic. Time to prove it works.
- Arrange your browser so you can see the dashboard at http://localhost:5173 and the Firebase Console side by side.
- In the Firebase Console, navigate to Firestore Database and open the hospitals collection.
- Click on any hospital document (for example, AIIMS Delhi).
- Find the availableBeds field and change its value to 2.
Before you click Save in the console, what do you expect to happen on the dashboard? Will you need to refresh the page?
- Click Save in the Firebase Console to commit the change.
The dashboard updates instantly. The AIIMS Delhi card now shows 2 available beds, and the status badge flips to red ("Full") because 2 out of 200 is below 10%. No page refresh needed.
Why did the status color change?
The getStatus function in HospitalCard.jsx calculates the color from the ratio of available beds to total beds. When onSnapshot delivers updated data, React re-renders the card with the new values. The status recalculates automatically.
- Try one more test. In the Firebase Console, click Add document in the hospitals collection.
- Click Auto-ID to generate a document ID.
- Add these fields: name (string) = Test Hospital, city (string) = Mumbai, state (string) = Maharashtra, totalBeds (number) = 100, availableBeds (number) = 50, icuAvailable (number) = 5, contact (string) = 000-0000000.
- Click Save.
A new "Test Hospital" card appears on the dashboard immediately. You did not refresh the page, and you did not write any code to handle new documents specifically.
- Clean up by deleting the test document. In the Firebase Console, click the test document you just created, then click the three-dot menu and select Delete document.
- Also reset the AIIMS Delhi availableBeds field back to 45 so your data matches the original seed.
Both changes reflect on the dashboard instantly. That is real-time data synchronization in action.
Dashboard not updating without a refresh?
- Make sure you replaced the entire useEffect body, not just the import. If the code still calls getDocs inside the effect, it will only fetch once.
- Verify that you saved src/App.jsx after making your changes. Vite auto-refreshes only when the file is saved.
- Check that the Firestore field type matches what the app expects. If you enter availableBeds as a string instead of a number in the console, the status calculation may not work correctly.
Need more help debugging?
Your dashboard is now a live window into the Firestore database. Any change to hospital data appears the instant it is saved, with zero manual refreshes. Next up, you will make the dashboard even more useful by adding search and state-based filtering so users can find specific hospitals fast.
Add Search, Filtering, and Deploy
Your dashboard now updates the moment someone changes a bed count in Firestore. However, with 10 hospitals on screen, imagine scaling to hundreds. Users need a way to narrow down results fast.
In this step, you'll add a search bar and a state dropdown so users can find hospitals instantly. Then you'll deploy the finished app to Firebase Hosting so anyone with the URL can access it.
In this step, get ready to:
- Add search and state-based filtering to the dashboard.
- Deploy the app to Firebase Hosting with a live public URL.
Add a search bar and state filter dropdown
Right now every hospital card sits in one big grid with no way to narrow results. A text input lets users search by hospital name or city, and a dropdown lets them filter by state.
- In src/App.jsx, find this line near the top of the App function:
- Reminder: your dev server should still be running from the previous step. If you stopped it, start it again with npm run dev from the arogyabed-india folder.
const [hospitals, setHospitals] = useState([]);
- Add these two new state variables directly below that line:
// Track the user's search text and selected state filter
const [searchTerm, setSearchTerm] = useState("");
const [selectedState, setSelectedState] = useState("All");
- Next, find the closing of the useEffect block. It looks like this:
}, []);
- Add this line directly below it to build the list of unique state names for the dropdown:
// Build a list of unique state names for the dropdown
const states = ["All", ...new Set(hospitals.map((h) => h.state))];
- Now find this section in the return statement:
</header>
<div className="hospital-grid">
- Replace those two lines with the following block, which adds the search input, state dropdown, and the hospital grid:
</header>
<div className="filters">
<input
type="text"
placeholder="Search hospitals or cities..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
<select
value={selectedState}
onChange={(e) => setSelectedState(e.target.value)}
className="state-select"
>
{states.map((state) => (
<option key={state} value={state}>
{state === "All" ? "All States" : state}
</option>
))}
</select>
</div>
<div className="hospital-grid">
What does this code do?
The searchTerm state tracks whatever the user types into the search input. The selectedState state tracks the dropdown selection, defaulting to "All".
The states array is built dynamically from your hospital data using a Set to extract unique state names. The "All" option is prepended so users can reset the filter.
- Save src/App.jsx.
- Switch to your browser. You should see a text input and a dropdown above the hospital cards. The dropdown lists state names like Delhi, Maharashtra, and Karnataka.
Don't see the search bar or dropdown?
- Make sure you placed the searchTerm and selectedState declarations inside the App function, directly below the existing hospitals state line.
- Check that the states derivation line sits after the useEffect closing bracket, not inside it.
- Confirm you saved the file. Unsaved changes will not trigger a hot reload.
help me debug my search bar and dropdown not appearing in App.jsx
Implement the filtering logic
The search bar and dropdown are visible, but they do not filter the hospital cards yet. You need a filteredHospitals variable that narrows results based on both inputs.
- In src/App.jsx, find the states line you just added. Add this filtering logic directly below it:
// Filter hospitals by search text and selected state
const filteredHospitals = hospitals.filter((hospital) => {
const matchesSearch =
hospital.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
hospital.city.toLowerCase().includes(searchTerm.toLowerCase());
const matchesState =
selectedState === "All" || hospital.state === selectedState;
return matchesSearch && matchesState;
});
- Next, find <div className="hospital-grid"> in the return statement. Add this results count line directly above it:
<p className="results-count">
Showing {filteredHospitals.length} of {hospitals.length} hospitals
</p>
- Finally, find this line in the hospital grid:
{hospitals.map((hospital) => (
- Change hospitals.map to filteredHospitals.map, so the line reads:
{filteredHospitals.map((hospital) => (
What does this filtering do?
The filteredHospitals variable runs two checks on every hospital. It matches the search term against the hospital name or city (case-insensitive). It also checks whether the selected state matches or if the user chose "All".
Only hospitals that pass both checks appear in the grid. The results count line tells the user how many hospitals are visible out of the total.
- Save src/App.jsx.
- In the browser, type AIIMS into the search bar. Only AIIMS Delhi should remain, and the results count should read "Showing 1 of 10 hospitals".
- Clear the search bar. Select Maharashtra from the dropdown. You should see only KEM Hospital and Sion Hospital.
Search or filter not working?
- Confirm you changed hospitals.map to filteredHospitals.map in the grid. If you still render hospitals, the filter logic runs but the display never changes.
- Check that the filteredHospitals variable is declared after states and before the return statement. Placing it inside the useEffect block will cause errors.
help me debug my hospital filtering logic in App.jsx
- Now add the CSS styles for the filter controls. In src/App.css, find the closing brace of the .header p rule. Add these styles directly below it:
.filters {
display: flex;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.search-input {
flex: 1;
min-width: 200px;
padding: 10px 16px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font-size: 1rem;
}
.state-select {
padding: 10px 16px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font-size: 1rem;
background: white;
}
.results-count {
color: #64748b;
margin-bottom: 16px;
font-size: 0.9rem;
}
What do these styles do?
The .filters container uses flexbox to lay the search input and dropdown side by side. The flex-wrap: wrap rule lets them stack on smaller screens.
Both the input and dropdown get matching rounded borders and padding so they look consistent. The .results-count is styled as a subtle grey line above the card grid.
- Save src/App.css.
- Your browser should refresh automatically. The search input and dropdown now have rounded borders and sit neatly above the cards.
✔️ Awesome, I've got everything!
Great. Confirm you have saved both src/App.jsx and src/App.css.
ⓧ I'd like to double check the full code
Scroll up through your work and confirm each piece is present in this order inside src/App.jsx:
- The three state declarations: hospitals, searchTerm, selectedState.
- The useEffect with onSnapshot (unchanged from the previous step).
- The states derivation line.
- The filteredHospitals filter block.
- The return with header, filters div (input + select), results count, and the grid rendering filteredHospitals.map.
For src/App.css, confirm the four new rules (.filters, .search-input, .state-select, .results-count) appear between the .header p rule and the .hospital-grid rule.
Deploy to Firebase Hosting
Your dashboard works locally, but nobody else can see it yet. Firebase Hosting lets you deploy a production build to a public URL with a few terminal commands.
- Stop the dev server by pressing Ctrl+C in the terminal.
- Install the Firebase CLI globally by running this command:
npm install -g firebase-tools
- Verify the Firebase CLI installed by running:
firebase --version
✔️ I see a version number
The Firebase CLI is ready. Move on to the next step.
ⓧ Command not found
The global install may not be on your system path. Try closing and reopening your terminal, then run firebase --version again.
If that does not help, try running the install with sudo on macOS/Linux:
sudo npm install -g firebase-tools
On Windows, open your terminal as Administrator and run the install command again.
- Log into Firebase from the terminal by running:
firebase login
What happens when you run this?
This command opens your default browser and asks you to sign in with your Google account. Sign in with the same account you used to create the Firebase project. After you grant permissions, the terminal prints a success message and you can close the browser tab.
- Initialize Firebase Hosting in your project by running:
firebase init hosting
What prompts will you see?
The CLI asks several questions. Here is how to answer each one:
- Select Use an existing project and choose the ArogyaBed project you created earlier.
- When asked "What do you want to use as your public directory?", type dist and press Enter. Vite outputs its production build to a folder called dist, not build.
- When asked "Configure as a single-page app (rewrite all urls to /index.html)?", type y and press Enter.
- When asked "Set up automatic builds and deploys with GitHub?", type N and press Enter.
You should see a success message and two new files appear in your project: firebase.json and .firebaserc.
- Build the production version of your app by running:
npm run build
Vite compiles and optimizes your React app into the dist folder. You should see output listing the generated files and their sizes.
- Before you deploy, what do you think the terminal will print when the deploy succeeds? Take a guess.
- Deploy your app to Firebase Hosting by running:
firebase deploy --only hosting
The CLI uploads your dist folder to Firebase's global CDN. When it finishes, it prints a Hosting URL that looks like your-project-id.web.app.
- Click the Hosting URL in your terminal or paste it into a new browser tab.
- You should see the full ArogyaBed India dashboard with the search bar, state dropdown, results count, and color-coded hospital cards.
- Try typing a hospital name in the search bar and selecting a state from the dropdown. The filtering should work exactly as it does locally.
Seeing a blank page at the Hosting URL?
- Open firebase.json and confirm that the public field is set to "dist", not "public" or "build". Vite outputs to dist by default.
- Make sure you ran npm run build before deploying. The dist folder must exist with your production files.
- If you changed the public directory after the initial setup, run firebase deploy --only hosting again to push the corrected configuration.
help me fix my blank Firebase Hosting deployment for a Vite React app
Your hospital bed tracker is live on the internet. Anyone with the URL can see real-time bed availability across Indian states. In the Secret Mission, you'll add authentication so only logged-in admins can update bed counts directly from the dashboard.
Secret mission
Add Admin Authentication
Right now, anyone with access to the Firebase Console can modify hospital data. In this secret mission, you will add Firebase Authentication so only logged-in admins can edit bed counts directly through the dashboard. Public visitors see the data, but only you can change it.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on Firebase's free Spark plan, so there are no ongoing costs.
Resources you used:
- Firebase project ArogyaBed on the Spark (free) plan, including Firestore database, Hosting site, and Authentication.
- Local arogyabed-india project folder on your Desktop.
- Firebase CLI (firebase-tools) installed globally via npm.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your live dashboard running.
- Your app remains live at its Firebase Hosting URL at no cost.
- Firestore provides 50,000 reads and 20,000 writes per day on the free Spark plan.
- Firebase Hosting provides 10 GB storage and 360 MB/day data transfer on the free Spark plan.
- The Firebase CLI (firebase-tools) stays installed on your machine for future projects.
Pause - I'll come back to this later
Firebase services on the Spark plan cannot be individually paused. With no traffic, your costs remain $0.
- Stop the local dev server if it is still running by pressing Ctrl+C in the terminal.
- Your Firebase project, Firestore data, and Hosting site remain active but generate no cost without traffic.
- When you are ready to resume, open the arogyabed-india folder in your editor and run npm run dev to start the local server again.
Delete - I don't want to use this again
Remove all project resources and start fresh.
- Return to the Firebase Console at console.firebase.google.com.
- Select the ArogyaBed project.
- Click the gear icon next to Project Overview.
- Select Project settings.
- Scroll to the bottom of the General tab.
- Click Delete project and confirm the deletion when prompted.
What does this remove?
Deleting the Firebase project removes your Firestore database, Hosting site, and Authentication data all at once. This action cannot be undone.
- Delete your local project folder. On macOS or Linux, run:
rm -rf ~/Desktop/arogyabed-india
- On Windows, run this instead:
Remove-Item -Recurse -Force ~\Desktop\arogyabed-india
- Uninstall the Firebase CLI by running:
npm uninstall -g firebase-tools
Nice Work!
Nice Work!
Nice work! You just built and deployed ArogyaBed India, a live hospital bed availability tracker powered by a cloud database with real-time sync.
You learned how to:
- Built a React dashboard connected to Cloud Firestore, migrating from hardcoded data to a cloud-hosted NoSQL database with color-coded status indicators for bed availability.
- Implemented real-time data synchronization using Firestore's onSnapshot listener, so dashboard updates appear instantly when data changes in the cloud.
- Added search and state-based filtering to make the dashboard usable at scale, then deployed the application to Firebase Hosting with a live public URL.
- Secret Mission: Added Firebase Authentication to gate admin editing capabilities, demonstrating role-based access patterns.
Ready to quiz yourself?