Skip to main content
listening-to-tauri-events Subscribe to and listen for Tauri v2 events in the frontend via @tauri-apps/api/event, including typed handlers and cleanup. USE WHEN listening to Rust-emitted events in a webview, distinguishing global vs webview-specific scopes, or unsubscribing listeners on unmount.
インストールへ移動 Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/Sheshiyer/skill-clusters --skill listening-to-tauri-eventsコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... このリポジトリの他の Skills Arcplume runs Grok through the Grok Build CLI's own OAuth-authenticated session (grok login) for image generation, with strict preflight validation, secret-safe handling, and headless CLI-driven execution -- no separate XAI_API_KEY billing. Video falls back to the billed xAI API. USE WHEN a user wants to generate an image via a locally logged-in Grok Build CLI session, e.g. 'generate an image with grok', 'use grok build', or 'use my logged-in grok session'.
Shared reference for the Selemene cluster: the two report surfaces (deterministic Rust reports vs. narrative witness-pipeline readings), the @selemene/bridge CLI contract, the output manifest format, and non-prescriptive witnessing tone. USE WHEN deciding which Selemene surface to invoke or when routing between birth/compatibility/transit reports and solo/dyadic readings.
Route Selemene Engine tasks to the right surface: deterministic reports (birth/compatibility/transit) via selemene-core and the @selemene/bridge CLI, or narrative witness readings via selemene-report. USE WHEN the user wants anything under the Selemene/Noesis umbrella but has not named the exact surface.
name listening-to-tauri-events description Subscribe to and listen for Tauri v2 events in the frontend via @tauri-apps/api/event, including typed handlers and cleanup. USE WHEN listening to Rust-emitted events in a webview, distinguishing global vs webview-specific scopes, or unsubscribing listeners on unmount. cluster tauri version 1.0.0
Listening to Tauri Events in the Frontend
This skill covers how to subscribe to events emitted from Rust in a Tauri v2 application using the @tauri-apps/api/event module.
Core Concepts
Tauri provides two event scopes:
Global events - Broadcast to all listeners across all webviews
Webview-specific events - Targeted to specific webview windows
Important: Webview-specific events are NOT received by global listeners. Use the appropriate listener type based on how events are emitted from Rust.
Installation
The event API is part of the core Tauri API package:
npm install @tauri-apps/api
pnpm add @tauri-apps/api
yarn add @tauri-apps/api
Global Event Listening
Basic Listen Pattern
import { listen } from ;
unlisten = ( , {
. ( , event. );
});
();
'@tauri-apps/api/event'
const
await
listen
'download-started'
(event ) =>
console
log
'Event received:'
payload
unlisten
Typed Event Payloads Define TypeScript interfaces matching your Rust payload structures:
import { listen } from '@tauri-apps/api/event' ;
interface DownloadStarted {
url : string ;
downloadId : number ;
contentLength : number ;
}
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 Object Structure The event callback receives an Event<T> object:
interface Event <T> {
event : string ;
id : number ;
payload : T;
}
Webview-Specific Event Listening For events emitted with emit_to or emit_filter targeting specific webviews:
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' ;
const appWebview = getCurrentWebviewWindow ();
const unlisten = await appWebview.listen <string >('login-result' , (event ) => {
if (event.payload === 'loggedIn' ) {
localStorage .setItem ('authenticated' , 'true' );
} else {
console .error ('Login failed:' , event.payload );
}
});
One-Time Event Listeners Use once for events that should only be handled a single time:
import { once } from '@tauri-apps/api/event' ;
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' ;
await once ('app-ready' , (event ) => {
console .log ('Application is ready' );
});
const appWebview = getCurrentWebviewWindow ();
await appWebview.once ('initialized' , (event ) => {
console .log ('Webview initialized' );
});
Cleanup and Unsubscription
Manual Cleanup Always unsubscribe listeners when they are no longer needed:
import { listen } from '@tauri-apps/api/event' ;
const unlisten = await listen ('my-event' , (event ) => {
});
unlisten ();
React Component Cleanup 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 ();
return () => {
if (unlisten) {
unlisten ();
}
};
}, []);
return <div > Listening for progress...</div > ;
}
Vue Composition API Cleanup 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 ();
}
});
}
};
Svelte Cleanup <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>
Automatic Listener Cleanup Tauri automatically clears listeners in these scenarios:
Page reload - All listeners are cleared
URL navigation - Listeners are cleared (except in SPA routers)
Warning: Single Page Application routers that do not trigger full page reloads will NOT automatically clean up listeners. You must manually unsubscribe.
Multiple Event Listeners
Listening to Multiple Events 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 () => {
unlisteners.forEach ((unlisten ) => unlisten ());
};
}
const cleanup = await setupDownloadListeners ();
cleanup ();
Type Definitions Reference
Creating Typed Event Helpers import { listen, once, Event } from '@tauri-apps/api/event' ;
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 };
}
export async function listenTyped<K extends keyof AppEvents >(
eventName : K,
handler : (event : Event <AppEvents [K]> ) => void
): Promise <() => void > {
return listen<AppEvents [K]>(eventName, handler);
}
const unlisten = await listenTyped ('download-started' , (event ) => {
console .log (event.payload .url );
});
Corresponding Rust Emit Code For reference, here is how events are emitted from Rust:
Global Emit 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 ();
}
Webview-Specific Emit use tauri::{AppHandle, Emitter};
#[tauri::command]
fn notify_window (app: AppHandle, window_label: &str , message: String ) {
app.emit_to (window_label, "notification" , message).unwrap ();
}
Filtered Emit 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 ();
}
Best Practices
Always define TypeScript interfaces for event payloads to catch type mismatches at compile time
Use serde(rename_all = "camelCase") in Rust structs to match JavaScript naming conventions
Store unlisten functions and call them during cleanup to prevent memory leaks
Use once for initialization events that only fire a single time
Match listener scope to emit scope - use webview-specific listeners for emit_to events
Centralize event type definitions in a shared module for consistency
Common Patterns
Event Bus Pattern import { 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 ();
React Hook Pattern 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;
}
function StatusDisplay ( ) {
const status = useTauriEvent<string >('app-status' , 'initializing' );
return <div > Status: {status}</div > ;
}