useTauriEvent ​
The useTauriEvent composable provides a reactive interface for listening to and emitting Tauri events, enabling real-time communication between your Rust backend and Vue frontend.
Signature ​
typescript
function useTauriEvent<T = unknown>(eventName: string): TauriEventReturn<T>;Parameters ​
eventName ​
- Type:
string - Required: Yes
- Description: The name of the Tauri event to listen for
Return Value ​
The composable returns an object with the following properties:
data ​
- Type:
Readonly<Ref<T | null>> - Description: Reactive reference containing the last received event payload
error ​
- Type:
Readonly<Ref<Error | null>> - Description: Reactive reference containing any error that occurred
startListening ​
- Type:
() => Promise<void> - Description: Function to start listening for events
stopListening ​
- Type:
() => void - Description: Function to stop listening for events
emit ​
- Type:
(payload: T) => Promise<void> - Description: Function to emit an event with the given payload
Usage Examples ​
Basic Event Listening ​
vue
<template>
<div>
<h3>Event Listener</h3>
<div>
<button @click="startListening">Start Listening</button>
<button @click="stopListening">Stop Listening</button>
</div>
<div v-if="error" class="error">Error: {{ error.message }}</div>
<div v-if="data">
<h4>Last Event Received:</h4>
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
</div>
</div>
</template>
<script setup>
const { data, error, startListening, stopListening } =
useTauriEvent("my-event");
</script>Event Emission ​
vue
<template>
<div>
<h3>Send Events</h3>
<input v-model="message" placeholder="Enter message" />
<button @click="sendMessage" :disabled="!message">Send Event</button>
<div v-if="error" class="error">Error: {{ error.message }}</div>
</div>
</template>
<script setup>
const message = ref("");
const { emit, error } = useTauriEvent("user-message");
const sendMessage = async () => {
if (message.value.trim()) {
await emit({
message: message.value,
timestamp: Date.now(),
});
message.value = "";
}
};
</script>Typed Events with TypeScript ​
vue
<template>
<div>
<h3>Chat Application</h3>
<div class="chat-controls">
<button @click="startListening">Connect</button>
<button @click="stopListening">Disconnect</button>
</div>
<div class="chat-messages">
<div v-for="msg in messages" :key="msg.id" class="message">
<strong>{{ msg.user }}:</strong> {{ msg.text }}
<small>{{
new Date(msg.timestamp).toLocaleTimeString()
}}</small>
</div>
</div>
<div class="chat-input">
<input
v-model="newMessage"
@keyup.enter="sendMessage"
placeholder="Type a message..."
/>
<button @click="sendMessage">Send</button>
</div>
</div>
</template>
<script setup>
interface ChatMessage {
id: string
user: string
text: string
timestamp: number
}
const messages = ref<ChatMessage[]>([])
const newMessage = ref('')
const {
data: receivedMessage,
error,
startListening,
stopListening,
emit
} = useTauriEvent<ChatMessage>('chat-message')
// Watch for new messages
watch(receivedMessage, (message) => {
if (message) {
messages.value.push(message)
}
})
const sendMessage = async () => {
if (newMessage.value.trim()) {
const message: ChatMessage = {
id: Date.now().toString(),
user: 'You',
text: newMessage.value,
timestamp: Date.now()
}
await emit(message)
newMessage.value = ''
}
}
// Auto-connect when component mounts
onMounted(() => {
startListening()
})
</script>File System Watcher ​
vue
<template>
<div>
<h3>File System Watcher</h3>
<div>
<button @click="startWatching">Start Watching</button>
<button @click="stopWatching">Stop Watching</button>
</div>
<div v-if="error" class="error">Error: {{ error.message }}</div>
<div v-if="fileEvent">
<h4>File System Event:</h4>
<p><strong>Action:</strong> {{ fileEvent.action }}</p>
<p><strong>File:</strong> {{ fileEvent.path }}</p>
<p>
<strong>Time:</strong>
{{ new Date(fileEvent.timestamp).toLocaleString() }}
</p>
</div>
<div class="recent-events">
<h4>Recent Events:</h4>
<ul>
<li v-for="event in recentEvents" :key="event.timestamp">
{{ event.action }} - {{ event.path }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
interface FileSystemEvent {
action: 'created' | 'modified' | 'deleted'
path: string
timestamp: number
}
const recentEvents = ref<FileSystemEvent[]>([])
const {
data: fileEvent,
error,
startListening: startWatching,
stopListening: stopWatching
} = useTauriEvent<FileSystemEvent>('file-system-event')
// Track recent events
watch(fileEvent, (event) => {
if (event) {
recentEvents.value.unshift(event)
// Keep only last 10 events
if (recentEvents.value.length > 10) {
recentEvents.value = recentEvents.value.slice(0, 10)
}
}
})
</script>Progress Tracking ​
vue
<template>
<div>
<h3>Download Progress</h3>
<button @click="startDownload" :disabled="isDownloading">
Start Download
</button>
<div v-if="isDownloading" class="progress-container">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${progress?.percentage || 0}%` }"
></div>
</div>
<p>{{ progress?.percentage || 0 }}% - {{ progress?.status }}</p>
<p v-if="progress?.bytesDownloaded">
{{ formatBytes(progress.bytesDownloaded) }} /
{{ formatBytes(progress.totalBytes) }}
</p>
</div>
<div v-if="error" class="error">
Download failed: {{ error.message }}
</div>
</div>
</template>
<script setup>
interface DownloadProgress {
percentage: number
status: string
bytesDownloaded: number
totalBytes: number
finished: boolean
}
const isDownloading = ref(false)
const {
data: progress,
error,
startListening,
stopListening,
emit
} = useTauriEvent<DownloadProgress>('download-progress')
const startDownload = async () => {
isDownloading.value = true
await startListening()
// Emit download start command
await emit({ action: 'start', url: 'https://example.com/file.zip' })
}
// Watch for download completion
watch(progress, (prog) => {
if (prog?.finished) {
isDownloading.value = false
stopListening()
}
})
const formatBytes = (bytes: number) => {
return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}
</script>
<style scoped>
.progress-container {
margin: 1rem 0;
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #4caf50;
transition: width 0.3s ease;
}
</style>Lifecycle Management ​
The composable automatically handles cleanup:
vue
<script setup>
const { startListening, stopListening } = useTauriEvent("my-event");
// Start listening when component mounts
onMounted(() => {
startListening();
});
// Cleanup is automatic when component unmounts
// No need to manually call stopListening in onUnmounted
</script>Error Handling ​
Handle different types of errors:
vue
<script setup>
const { error, startListening } = useTauriEvent("my-event");
watchEffect(() => {
if (error.value) {
if (error.value.message.includes("Tauri API not available")) {
console.log("Running in browser mode - events disabled");
} else {
console.error("Event error:", error.value);
// Maybe show a user notification
}
}
});
</script>Best Practices ​
1. Use TypeScript for Event Payloads ​
Define interfaces for your event data:
typescript
interface UserAction {
type: "login" | "logout" | "register";
userId: string;
timestamp: number;
}
const { data, emit } = useTauriEvent<UserAction>("user-action");2. Handle Connection States ​
Provide feedback about listening state:
vue
<template>
<div>
<span class="status" :class="{ listening: isListening }">
{{ isListening ? "Connected" : "Disconnected" }}
</span>
</div>
</template>
<script setup>
const isListening = ref(false);
const { startListening, stopListening } = useTauriEvent("status");
const connect = async () => {
await startListening();
isListening.value = true;
};
const disconnect = () => {
stopListening();
isListening.value = false;
};
</script>3. Debounce Rapid Events ​
For high-frequency events, consider debouncing:
vue
<script setup>
import { debounce } from "lodash-es";
const { data } = useTauriEvent("mouse-position");
const debouncedHandler = debounce((position) => {
// Handle position update
console.log("Mouse position:", position);
}, 100);
watch(data, debouncedHandler);
</script>4. Validate Event Data ​
Always validate incoming event data:
vue
<script setup>
const { data } = useTauriEvent("user-data");
watch(data, (userData) => {
if (userData && typeof userData === "object" && "id" in userData) {
// Data is valid
handleUserData(userData);
} else {
console.warn("Invalid user data received:", userData);
}
});
</script>Rust Backend Integration ​
Emitting Events from Rust ​
rust
use tauri::{AppHandle, Manager};
#[tauri::command]
fn process_data(app: AppHandle) -> Result<(), String> {
// Do some processing...
// Emit event to frontend
app.emit_all("data-processed", ProcessedData {
id: 123,
result: "Success".to_string(),
timestamp: chrono::Utc::now().timestamp(),
}).map_err(|e| e.to_string())?;
Ok(())
}Listening to Events in Rust ​
rust
use tauri::{Manager, Window};
fn setup_event_listeners(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let window = app.get_window("main").unwrap();
window.listen("user-action", |event| {
println!("Received user action: {:?}", event.payload());
// Handle the event
});
Ok(())
}Related ​
- useTauriInvoke - For executing Tauri commands
- Examples - See more usage examples