Daily Focus is a local-first task management app built with React, TypeScript, Dexie, and TanStack Query.
It helps you capture, complete, and review daily tasks with a clean UI and instant data updates.
The app uses TanStack Query as the data orchestration layer across the entire UI.
Even though persistence is local (IndexedDB via Dexie), TanStack Query gives the app:
- a centralized cache for tasks
- consistent loading and mutation states
- automatic UI refresh through query invalidation
- reusable data hooks (
useTasks,useAddTask,useUpdateTask,useDeleteTask)
This architecture keeps components simple and makes it easy to switch from local DB to remote APIs later.
- React 19 + TypeScript
- Vite
- React Router
- TanStack Query
- Dexie (IndexedDB)
- Tailwind CSS + Radix-style UI primitives
- ESLint
The project follows a layered frontend architecture:
- UI Layer (
components,pages)
Renders views, handles user interactions, and consumes query hooks. - Query Layer (
queries/task.queries.ts)
Encapsulates read/write operations withuseQueryanduseMutation. - Service Layer (
services/taskService.ts)
Defines data access methods (getAllTasks,addTask,updateTask, etc.). - Persistence Layer (
data/db.ts)
Dexie schema and IndexedDB setup. - Domain Layer (
types/task.ts)
Shared task types used across app layers.
HomePage/TaskItem -> TanStack Query hooks -> taskService -> Dexie -> IndexedDB
^
|
Query cache + invalidation
TanStack Query is initialized at application bootstrap with QueryClientProvider.
useTasks()queryKey: ['tasks']- Returns all tasks for the main list and summary cards.
useTask(id)queryKey: ['task', id]- Returns a single task by id.
useAddTask()- Adds a task through the service layer.
- Invalidates
['tasks']on success.
useUpdateTask()- Updates a task (for completion toggle).
- Invalidates
['tasks']and['task', id].
useDeleteTask()- Deletes a task.
- Invalidates
['tasks']and['task', id].
Instead of manually synchronizing component state after every write, mutations trigger invalidation and TanStack Query refetches fresh data. This keeps UI state trustworthy and reduces bugs caused by stale local state.
.
├─ components.json
├─ eslint.config.js
├─ index.html
├─ package.json
├─ README.md
├─ tsconfig.app.json
├─ tsconfig.json
├─ tsconfig.node.json
├─ vite.config.ts
├─ dev-dist/
├─ public/
│ └─ vite.svg
└─ src/
├─ App.css
├─ App.tsx
├─ index.css
├─ main.tsx
├─ components/
│ ├─ Layout.tsx
│ ├─ TaskItem.tsx
│ └─ ui/
│ ├─ badge.tsx
│ ├─ button.tsx
│ ├─ card.tsx
│ ├─ input.tsx
│ ├─ tabs.tsx
│ └─ textarea.tsx
├─ data/
│ └─ db.ts
├─ lib/
│ └─ utils.ts
├─ pages/
│ └─ HomePage.tsx
├─ queries/
│ └─ task.queries.ts
├─ routes/
│ ├─ AppRoutes.tsx
│ └─ index.tsx
├─ services/
│ └─ taskService.ts
└─ types/
└─ task.ts
- Node.js 18+
- npm
npm installnpm run devnpm run buildnpm run previewnpm run lintnpm run dev: starts Vite dev servernpm run build: TypeScript build + Vite production buildnpm run preview: serves production bundle locallynpm run lint: runs ESLint
Task fields:
id?: numbertitle: stringdescription?: stringcompleted: booleancreatedAt: numbercategory?: string
- Add tasks with title and optional description
- Toggle task completion
- Delete tasks
- Filter tasks by all/completed/pending
- Dashboard counters (total/completed/pending)
- Local persistence with IndexedDB
- Automatic UI synchronization through TanStack Query cache invalidation
If you later move from Dexie to a backend API, most UI code can remain unchanged. You mainly replace service methods while keeping query keys and mutation patterns.
Private project.