A simple task manager demonstrating Firestate's key features in a React application.
- Document subscription - Task list name syncs in real-time
- Collection subscription - Tasks are automatically synced
- Undo/Redo - Full undo/redo support with keyboard shortcuts
- Sync indicators - Visual feedback for save status
- CRUD operations - Add, update, and delete tasks
- Optimistic updates - Changes appear immediately
- Go to the Firebase Console
- Create a new project (or use an existing one)
- Enable Cloud Firestore:
- Navigate to Build > Firestore Database
- Click "Create database"
- Start in test mode for development
- In Firebase Console, go to Project Settings (gear icon)
- Scroll down to "Your apps"
- Click "Add app" and select Web (</>)
- Register your app and copy the config values
- Copy the example env file and fill in your credentials:
cp .env.example .envEdit .env (gitignored) with your Firebase web app config:
VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789
VITE_FIREBASE_APP_ID=1:123456789:web:abcdef123456# Install dependencies
pnpm install
# Start development server
pnpm devThe app will be available at http://localhost:5173.
- Create a task list - Click the button to create your first task list
- Edit the title - Click on the list name to edit it
- Add tasks - Type in the input and press Enter or click Add
- Toggle completion - Click the checkbox
- Change priority - Use the dropdown to set Low/Medium/High
- Delete tasks - Click the Delete button
- Undo/Redo - Use the buttons or
Ctrl/Cmd+ZandCtrl/Cmd+Y
Open the app in multiple browser tabs to see real-time synchronization in action. Changes made in one tab will appear instantly in others.
src/
├── firebase.ts # Firebase initialization (reads from .env)
├── schemas.ts # Firestate schema definitions
├── App.tsx # Main React component
└── main.tsx # React entry point
// Define your data shape with a TypeScript interface
interface Task {
title: string
completed: boolean
priority: 'low' | 'medium' | 'high'
createdAt: number
}
// Create a collection definition
const tasksCollection = defineCollection<Task>({
path: (params) => `taskLists/${params.listId}/tasks`,
autosave: 500,
})A Zod schema can be passed via the schema field. Firestate runs it on
set and add writes so bad data throws at the call site.
// Subscribe to the collection
const tasks = useCollection({ definition: tasksCollection, params })
// Update a task
tasks.update({ [taskId]: { completed: true } })
// Add a new task
tasks.add('new-id', { title: 'New Task', completed: false, ... })
// Remove a task
tasks.remove(taskId)const { undo, redo, canUndo, canRedo } = useUndoManager()
// Enable keyboard shortcuts
useUndoKeyboardShortcuts()- Firestate Documentation
- Firebase Documentation
- Zod (schema and runtime validation)