Development Guides
Build Frontend Components
Guide for adding new React components to the desktop frontend.
Prerequisites
- Understand the frontend component tree, state management, and provider hierarchy
- Understand the dumb pipe principle: all business logic lives in Rust — React handles only UI/UX
Key Principles
- No domain logic in TypeScript — validation, data transformation, and business rules belong in Rust use cases
- All data via
invoke()— every read/write goes through Tauri IPC to the backend - Zustand for cross-component state — use
appStorefor UI state (navigation, dialogs, toasts, layout) - React Context for lifecycle state —
WorkspaceProviderandSyncProviderwrap the app
Step 1: Choose the Right Location
src-react/components/├── layout/ # App shell structure (sidebar, panels)├── editor/ # Editor and extensions├── context/ # Right-panel context components├── tags/ # Tag management├── settings/ # Settings panels├── attachments/ # Attachment browser├── auth/ # Auth UI├── sync/ # Sync status├── CommandPalette/ # Command palette├── FirstLaunch/ # Onboarding└── [Dialog components]Place your component in the appropriate feature directory. If it doesn’t fit, create a new directory.
Step 2: Data Fetching Pattern
Use invoke() with Specta-generated type-safe bindings:
import { commands } from "@/lib/bindings";
// Type-safe — return type and parameters are generated from Rustconst page = await commands.getPage(slug);For components that need reactive data, use a refresh key pattern:
const pageTreeKey = useAppStore((s) => s.pageTreeKey);
useEffect(() => { // Re-fetch when pageTreeKey changes (after mutations elsewhere) loadPages();}, [pageTreeKey]);Step 3: Error Handling
import { parseCommandError } from "@/lib/errors";
try { await commands.createPage({ title, parentSlug });} catch (err) { addToast(parseCommandError(err)); // Always user-friendly}See Error Handling Patterns for the full error hierarchy and frontend handling.
Step 4: State Management
For local component state, use React useState. For cross-component state, use the Zustand stores:
import { useAppStore } from "@/stores/appStore";
// Read stateconst selectedSlug = useAppStore((s) => s.selectedPageSlug);
// Dispatch actionsconst { selectPage, addToast } = useAppStore();See apps/desktop/src-react/stores/appStore.ts for the full store reference.
Step 5: Connect to Backend Commands
If your component needs a new backend command, follow Adding a Tauri Command to create the full vertical slice.
Checklist
- Component placed in correct feature directory
- All data fetched via
invoke()/ Specta bindings (no direct DB/filesystem access) - Errors handled with
parseCommandError(), shown via toast - Cross-component state uses
appStore(not prop drilling) - No business logic in TypeScript (validation, rules in Rust)
-
pnpm typecheckpasses
Related Documentation
- Error Handling Patterns — Error flow from domain to UI
- Adding a Tauri Command — Backend command wiring
Was this page helpful?
Thanks for your feedback!