Loading...
Loading...
Teaches how to subscribe to and listen for Tauri events in the frontend using the events API, including typed event handlers and cleanup patterns.
npx skill4agent add dchuk/claude-code-tauri-skills listening-to-tauri-events@tauri-apps/api/eventnpm install @tauri-apps/api
# or
pnpm add @tauri-apps/api
# or
yarn add @tauri-apps/apiimport { listen } from '@tauri-apps/api/event';
// Listen for a global event
const unlisten = await listen('download-started', (event) => {
console.log('Event received:', event.payload);
});
// Clean up when done
unlisten();import { listen } from '@tauri-apps/api/event';
// Define the payload type (matches Rust struct with camelCase)
interface DownloadStarted {
url: string;
downloadId: number;
contentLength: number;
}
// Use generic type parameter for type safety
const unlisten = await listen<DownloadStarted>('download-started', (event) => {
console.log(`Downloading from ${event.payload.url}`);
console.log(`Content length: ${event.payload.contentLength} bytes`);
console.log(`Download ID: ${event.payload.downloadId}`);
});Event<T>interface Event<T> {
/** Event name */
event: string;
/** Event identifier */
id: number;
/** Event payload (your custom type) */
payload: T;
}emit_toemit_filterimport { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
// Listen for events targeted to this specific webview
const unlisten = await appWebview.listen<string>('login-result', (event) => {
if (event.payload === 'loggedIn') {
localStorage.setItem('authenticated', 'true');
} else {
console.error('Login failed:', event.payload);
}
});onceimport { once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// Global one-time listener
await once('app-ready', (event) => {
console.log('Application is ready');
});
// Webview-specific one-time listener
const appWebview = getCurrentWebviewWindow();
await appWebview.once('initialized', (event) => {
console.log('Webview initialized');
});import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('my-event', (event) => {
// Handle event
});
// Later, when done listening
unlisten();import { useEffect } from 'react';
import { listen } from '@tauri-apps/api/event';
interface ProgressPayload {
percent: number;
message: string;
}
function DownloadProgress() {
useEffect(() => {
let unlisten: (() => void) | undefined;
const setupListener = async () => {
unlisten = await listen<ProgressPayload>('download-progress', (event) => {
console.log(`Progress: ${event.payload.percent}%`);
});
};
setupListener();
// Cleanup when component unmounts
return () => {
if (unlisten) {
unlisten();
}
};
}, []);
return <div>Listening for progress...</div>;
}import { onMounted, onUnmounted } from 'vue';
import { listen } from '@tauri-apps/api/event';
interface NotificationPayload {
title: string;
body: string;
}
export default {
setup() {
let unlisten: (() => void) | undefined;
onMounted(async () => {
unlisten = await listen<NotificationPayload>('notification', (event) => {
console.log(`${event.payload.title}: ${event.payload.body}`);
});
});
onUnmounted(() => {
if (unlisten) {
unlisten();
}
});
}
};<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { listen } from '@tauri-apps/api/event';
interface StatusPayload {
status: 'idle' | 'loading' | 'complete';
}
let unlisten: (() => void) | undefined;
onMount(async () => {
unlisten = await listen<StatusPayload>('status-change', (event) => {
console.log('Status:', event.payload.status);
});
});
onDestroy(() => {
if (unlisten) {
unlisten();
}
});
</script>import { listen } from '@tauri-apps/api/event';
interface DownloadEvent {
url: string;
}
interface ProgressEvent {
percent: number;
}
interface CompleteEvent {
path: string;
size: number;
}
async function setupDownloadListeners() {
const unlisteners: Array<() => void> = [];
unlisteners.push(
await listen<DownloadEvent>('download-started', (event) => {
console.log(`Started downloading: ${event.payload.url}`);
})
);
unlisteners.push(
await listen<ProgressEvent>('download-progress', (event) => {
console.log(`Progress: ${event.payload.percent}%`);
})
);
unlisteners.push(
await listen<CompleteEvent>('download-complete', (event) => {
console.log(`Complete: ${event.payload.path} (${event.payload.size} bytes)`);
})
);
// Return cleanup function
return () => {
unlisteners.forEach((unlisten) => unlisten());
};
}
// Usage
const cleanup = await setupDownloadListeners();
// Later...
cleanup();import { listen, once, Event } from '@tauri-apps/api/event';
// Define all your event types in one place
export interface AppEvents {
'download-started': { url: string; downloadId: number };
'download-progress': { downloadId: number; percent: number };
'download-complete': { downloadId: number; path: string };
'download-error': { downloadId: number; error: string };
'notification': { title: string; body: string };
}
// Type-safe listen helper
export async function listenTyped<K extends keyof AppEvents>(
eventName: K,
handler: (event: Event<AppEvents[K]>) => void
): Promise<() => void> {
return listen<AppEvents[K]>(eventName, handler);
}
// Usage
const unlisten = await listenTyped('download-started', (event) => {
// event.payload is typed as { url: string; downloadId: number }
console.log(event.payload.url);
});use tauri::{AppHandle, Emitter};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadStarted {
url: String,
download_id: usize,
content_length: usize,
}
#[tauri::command]
fn start_download(app: AppHandle, url: String) {
app.emit("download-started", DownloadStarted {
url,
download_id: 1,
content_length: 1024,
}).unwrap();
}use tauri::{AppHandle, Emitter};
#[tauri::command]
fn notify_window(app: AppHandle, window_label: &str, message: String) {
// Emit to a specific webview by label
app.emit_to(window_label, "notification", message).unwrap();
}use tauri::{AppHandle, Emitter, EventTarget};
#[tauri::command]
fn broadcast_to_editors(app: AppHandle, content: String) {
app.emit_filter("content-update", content, |target| {
match target {
EventTarget::WebviewWindow { label } => {
label.starts_with("editor-")
}
_ => false,
}
}).unwrap();
}serde(rename_all = "camelCase")onceemit_toimport { listen, Event } from '@tauri-apps/api/event';
type EventHandler<T> = (payload: T) => void;
class TauriEventBus {
private unlisteners: Map<string, () => void> = new Map();
async subscribe<T>(event: string, handler: EventHandler<T>): Promise<void> {
if (this.unlisteners.has(event)) {
this.unlisteners.get(event)!();
}
const unlisten = await listen<T>(event, (e: Event<T>) => {
handler(e.payload);
});
this.unlisteners.set(event, unlisten);
}
unsubscribe(event: string): void {
const unlisten = this.unlisteners.get(event);
if (unlisten) {
unlisten();
this.unlisteners.delete(event);
}
}
unsubscribeAll(): void {
this.unlisteners.forEach((unlisten) => unlisten());
this.unlisteners.clear();
}
}
export const eventBus = new TauriEventBus();import { useEffect, useState } from 'react';
import { listen, Event } from '@tauri-apps/api/event';
export function useTauriEvent<T>(eventName: string, initialValue: T): T {
const [value, setValue] = useState<T>(initialValue);
useEffect(() => {
let unlisten: (() => void) | undefined;
listen<T>(eventName, (event: Event<T>) => {
setValue(event.payload);
}).then((fn) => {
unlisten = fn;
});
return () => {
if (unlisten) {
unlisten();
}
};
}, [eventName]);
return value;
}
// Usage
function StatusDisplay() {
const status = useTauriEvent<string>('app-status', 'initializing');
return <div>Status: {status}</div>;
}