Tauri 2 IPC: Commands, Events, State — The Three-Way Bridge | Ep2
0views
C
CelesteAI
Description
Episode 2 of *Tauri Patterns for Production* — building the three-way bridge between Rust and your frontend.
Tauri 2 has three IPC primitives that, together, cover almost every desktop-app interaction:
- **Commands** — JS calls a Rust function and gets a return value (`#[tauri::command]` + `invoke()`).
- **Events** — Rust pushes data to JS without being asked (`emit()` + `listen()`).
- **State** — shared mutable data injected into commands (`tauri::State of Mutex`).
In this episode we scaffold a fresh project from scratch (`pnpm create tauri-app`), open `lib.rs` and `App.tsx` in nvim, and build a small **Pulse Monitor** desktop app that exercises all three primitives end to end.
What You'll Learn:
- Scaffold a Tauri 2 project non-interactively: `pnpm create tauri-app pulse-monitor --template react-ts --identifier com.codegiz.pulsemonitor --tauri-version 2 -y`. One command, zero prompts.
- Define shared state behind a Mutex and register it on the builder with `.manage(Mutex::new(...))`. Commands receive it as `State of Mutex` automatically.
- Write multiple `#[tauri::command]` functions that read and mutate state — `start_pulse`, `stop_pulse`, `reset_pulse`. Each one is callable from React via `invoke()`.
- Spawn a background thread inside `.setup()` that wakes once per second, reads state, and calls `app_handle.emit("pulse", payload)`. This is how Rust pushes to the frontend.
- Subscribe to Rust-emitted events on the React side with `listen typed ("pulse", handler)` from `@tauri-apps/api/event`. The `unlisten` cleanup matters — return it from `useEffect`.
- Wire it all together: a heartbeat dial that ticks once per second while running, with Start / Stop / Reset buttons that invoke commands and a state Mutex shared across all of them.
Timestamps:
0:00 - Intro
0:23 - Preview — the three IPC primitives
1:00 - Step 1: Scaffold (pnpm create tauri-app)
1:30 - Step 2: Install dependencies
1:55 - Step 3: Verify Tauri 2
2:10 - Step 4: Project structure
2:30 - Step 5: nvim — Rust backend (state + commands + events)
6:00 - Step 6: nvim — React frontend (listen + invoke)
8:30 - Step 7: Build for production
8:50 - Live demo — running Pulse Monitor
9:15 - Recap
10:00 - End screen
Key Takeaways:
1. **Three primitives cover almost every interaction.** Commands for "JS asks, Rust answers." Events for "Rust speaks, JS listens." State for "data shared across both." Almost any desktop-app feature decomposes into a combination of these three. Long-running tasks with progress = command + event. Real-time dashboards = state + event. Forms = command + state.
2. **State is registered on the builder with `.manage()`.** Wrap your shared data in a `Mutex` (or `RwLock` for read-heavy access) and call `.manage(Mutex::new(MyState { ... }))` once on the builder. From then on, any command that takes `state: State of Mutex of MyState` as a parameter receives it automatically. Tauri injects it. No globals, no `lazy_static`, no thread-local trickery.
3. **Events flow either direction.** `app.emit("event-name", payload)` from Rust delivers to all listeners on the JS side. `listen typed ("event-name", handler)` on the JS side subscribes; the returned promise resolves to an `unlisten` function — call it on cleanup. The other direction (`emit` from JS, `listen` in Rust) also works, but the most common pattern is Rust to JS for progress, telemetry, and live data.
4. **`.setup()` is where background tasks belong.** The `.setup(|app| { ... })` builder hook runs once at app startup, has access to the `AppHandle`, and is the right place to spawn threads, start watchers, or initialize anything that needs the app to be alive. Clone the `AppHandle` before moving it into a thread; from there, the thread can `.state()` to read managed state and `.emit()` to push events.
5. **The frontend listener lifecycle matters.** `listen()` returns a `Promise of UnlistenFn`. In React, subscribe in `useEffect`, store the promise, and return a cleanup that resolves the promise and calls the unlisten function. Forgetting cleanup leaks subscriptions across hot reloads in dev and across component unmounts in prod.
This channel is run by Claude AI. Tutorials AI-produced; reviewed and published by Codegiz. Source code at codegiz.com.
#Tauri #Tauri2 #Rust #DesktopApp #React #TypeScript #IPC #Neovim #ClaudeAI