• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License
  • Auth Overview
  • Global Configuration
    • Setting up your Firebase Project
    • Setting up Firebase Functions
  • Writing data to Firestore
  • Commands
  • Introduction
  • Production Checklist
  • Introduction
  • Overview
  • Stripe Configuration
  • Running Tests
  • Introduction
  • Setting up Firebase Auth
  • Fetching data from Firestore
  • Technical Details
  • Extending Organizations
  • Stripe Webhooks
  • CI Tests
  • Initial Setup
  • React Hooks
  • Auth Flow
  • API requests
  • Code Style
  • Clone the repository
  • Security Rules
  • User Permissions
  • Limitations
  • Project Structure
  • Third-Party Providers
  • Reading data from Storage
  • Running the application
  • Subscription Permissions
  • One-Time Payments
  • Running the App
  • Email Link Authentication
  • Uploading data to Storage
  • Security Rules
  • Migrate to Lemon Squeezy
  • Project Configuration
  • Multi-Factor Authentication
  • Writing your own Fetch
  • Translations and Locales
  • Coding Conventions
  • Environment Variables
  • Architecture and Folder Structure
    • Structure your Application
    • Data Model
  • Requiring Email verification
  • Sending Emails
  • Tailwind CSS and Styling
  • Validating API payload with Zod
  • Authentication
  • Onboarding Flow
  • Logging
  • Development: adding custom features
  • Prevent abuse with AppCheck
  • Enable CORS
  • Encrypting Secrets
  • User Roles
  • Firestore: Data Fetching
  • Custom React Hooks
  • Custom React Hooks
  • Firestore: Data Writing
  • Troubleshooting
  • Forms
  • Application Pages
  • API Routes
  • API Routes Validation
  • Translations
  • Adding pages to the Marketing Site
  • Deploying to Production
  • Updating to the latest version
This kit is no longer maintained.

Firestore: Data Fetching

Learn how to fetch data from Firestore in your React applications.

Before writing the Components, we want to tackle the task of fetching data from Firestore.

This involves a couple of steps:

  1. We need to define, on paper, what the data model of the Firestore collections looks like
  2. We need to update the Firestore rules so we can read and write data to the collection
  3. We need to write the hooks to fetch data from Firebase Firestore

Firestore Data Model

First, let's write the data model. But... what does it mean?

When thinking about the Firestore data model, we need to answer the following questions:

  1. Where does the data live?
  2. How is the data structured?
  3. How do we protect the data with security rules?

Ok, let's reply to these questions.

Where does the data live?

We can place tasks as a Firestore top-level collection. Because tasks belong to an organization, we can ensure that only users of that organization can read and write those tasks by adding a foreign key to each task named organizationId.

How is the data structured?

We can define a Task model at src/lib/tasks/types/task.ts:

src/lib/tasks/types/task.ts
export interface Task {
name: string;
description: string;
organizationId: string;
dueDate: string;
done: boolean;
}
How do we protect the data with security rules?

To write our Security Rules, we will update the file firestore.rules:

text
match /tasks/{taskId} {
allow create: if userIsMemberByOrganizationId(incomingData().organizationId);
allow read, update, delete: if userIsMemberByOrganizationId(existingData().organizationId);
}

The function userIsMemberByOrganizationId is pre-defined in the Makerkit's template: basically, it will verify that the current user is a member of the organization with ID organizationId.

If you use VSCode, take a look at the extension toba.vsfire.

Data Fetching: React Hooks to read data from Firestore

Now that our security rules are updated, we can write Hooks to read data from Firestore.

I recommend writing your entities' hooks at src/lib/tasks/hooks. Let's start with a React Hook to fetch all the organization's tasks:

src/lib/tasks/hooks/use-fetch-tasks.ts
import { useFirestore, useFirestoreCollectionData } from 'reactfire';
import {
collection,
CollectionReference,
query,
where,
} from 'firebase/firestore';
import { Task } from '~/lib/tasks/types/task';
function useFetchTasks(organizationId: string) {
const firestore = useFirestore();
const tasksCollection = 'tasks';
const collectionRef = collection(
firestore,
tasksCollection
) as CollectionReference<WithId<Task>>;
const path = `organizationId`;
const operator = '==';
const constraint = where(path, operator, organizationId);
const organizationsQuery = query(collectionRef, constraint);
return useFirestoreCollectionData(organizationsQuery, {
idField: 'id',
});
}
export default useFetchTasks;

Let's take a deep breath and slowly go through the above hook.

  1. The hook above uses useFirestoreCollectionData, a React hook from the library reactfire to fetch real-time collection data from Firestore
  2. We build a query that checks that the organizationId parameter matches the organizationId property of each task
  3. Finally, we add { idField: 'id' }. This special parameter decorates the data with the ID of the document. Thanks to the interface WithId, we add an id property to the Task interface returned from Firestore

Displaying Firestore data in Components

Now that we can fetch our data using the hook useFetchTasks, we can build a component that lists each Task.

To do so, let's create a new component named TasksListContainer:

components/tasks/TasksListContainer.tsx
import PageLoadingIndicator from '~/core/ui/PageLoadingIndicator';
import Alert from '~/core/ui/Alert';
import Heading from '~/core/ui/Heading';
import Button from '~/core/ui/Button';
import useFetchTasks from '~/lib/tasks/hooks/use-fetch-tasks';
import TasksList from '~/components/tasks/TasksList';
import { Task } from '~/lib/tasks/types/task';
const TasksContainer: React.FC<{
organizationId: string;
}> = ({ organizationId }) => {
const { status, data: tasks } = useFetchTasks(organizationId);
if (status === `loading`) {
return <PageLoadingIndicator>Loading Tasks...</PageLoadingIndicator>;
}
if (status === `error`) {
return (
<Alert type={'error'}>
Sorry, we encountered an error while fetching your tasks.
</Alert>
);
}
if (tasks.length === 0) {
return <TasksEmptyState />;
}
return (
<div className={'flex flex-col space-y-4'}>
<div className={'mt-2 flex justify-end'}>
<CreateTaskButton>New Task</CreateTaskButton>
</div>
<div className={'flex flex-col space-y-4'}>
{tasks.map((task) => {
return <TaskListItem task={task} key={task.id} />;
})}
</div>
</div>
);
};
function TasksEmptyState() {
return (
<div
className={
'flex flex-col items-center justify-center space-y-4 h-full p-24'
}
>
<div>
<Heading type={5}>No tasks found</Heading>
</div>
<CreateTaskButton>Create your first Task</CreateTaskButton>
</div>
);
}
function TasksList({ tasks }: React.PropsWithChildren<{
tasks: Task[]
}>) {
return (
<div className={'flex flex-col space-y-4'}>
{tasks.map((task) => {
return <TaskListItem task={task} key={task.id} />;
})}
</div>
);
}
export default TasksList;
function CreateTaskButton(props: React.PropsWithChildren) {
return <Button href={'/tasks/new'}>{props.children}</Button>;
}
export default TasksContainer;

The data above will automatically update when the tasks collection changes!

Since the component "TaskListItem" is pretty complex and requires more files, we will define it separately. You will find it below after we explain the mutation hooks that the component will use.