Back to Blog

Tauri Events: Real-Time Bidirectional Communication | Rust + React Tutorial (Lesson 34)

Sandy LaneSandy Lane

Video: Tauri Events: Real-Time Bidirectional Communication | Rust + React Tutorial (Lesson 34) by Taught by Celeste AI - AI Coding Coach

Watch full page →

Tauri Events: Real-Time Bidirectional Communication | Rust + React Tutorial (Lesson 34)

Discover how to implement real-time, bidirectional communication between your Rust backend and React frontend using Tauri events. This tutorial explains the difference between commands and events, shows how to emit events from Rust, and how to listen to them in React with custom hooks to build dynamic UIs like progress bars and toast notifications.

Code

/// Rust: Emitting events from the backend using Tauri

use serde::Serialize;
use tauri::{AppHandle, Manager, Wry, Window};
use tauri::async_runtime::spawn;
use tauri::api::event::Emitter;

#[derive(Serialize)]
struct ProgressPayload {
  progress: u8,
  message: String,
}

#[tauri::command]
fn start_long_task(app: AppHandle) {
  // Spawn a thread to simulate a long-running task emitting progress events
  spawn(async move {
    for i in 0..=100 {
      let payload = ProgressPayload {
        progress: i,
        message: format!("Progress: {}%", i),
      };
      // Emit event "progress-update" to all windows
      app.emit_all("progress-update", payload).unwrap();
      // Simulate work
      std::thread::sleep(std::time::Duration::from_millis(50));
    }
    // Emit completion event
    app.emit_all("task-complete", "Download finished!").unwrap();
  });
}
// React + TypeScript: Listening to Tauri events with hooks

import { useEffect, useState } from "react";
import { listen, UnlistenFn } from "@tauri-apps/api/event";

interface ProgressPayload {
  progress: number;
  message: string;
}

export function useProgress() {
  const [progress, setProgress] = useState(0);
  const [message, setMessage] = useState("");

  useEffect(() => {
    // Listen for progress updates
    let unlistenProgress: UnlistenFn;
    let unlistenComplete: UnlistenFn;

    async function setupListeners() {
      unlistenProgress = await listen("progress-update", event => {
        setProgress(event.payload.progress);
        setMessage(event.payload.message);
      });
      unlistenComplete = await listen("task-complete", event => {
        setMessage(event.payload);
        // Optionally trigger a toast notification here
      });
    }
    setupListeners();

    // Cleanup listeners on unmount
    return () => {
      unlistenProgress && unlistenProgress();
      unlistenComplete && unlistenComplete();
    };
  }, []);

  return { progress, message };
}

Key Points

  • Commands in Tauri follow a request-response pattern, while events enable the backend to push updates proactively to the frontend.
  • Use app.emit_all(eventName, payload) in Rust to emit events with typed payloads serialized to JSON.
  • In React, listen to Tauri events with the listen API inside useEffect, ensuring to unregister listeners on cleanup.
  • Typed event payloads in TypeScript provide type safety and improve developer experience with autocomplete.
  • Combining Tauri events with UI libraries like shadcn/ui allows building responsive components like progress bars and toast notifications that update in real time.