How to Subscribe to iLoader Operation Events in a Custom React Component

Use Tauri's listen function from @tauri-apps/api/event to subscribe to the "operation" channel and handle OperationUpdate payloads in your component's useEffect hook.

iLoader drives long-running tasks through a centralized operation system that emits Tauri events from the Rust backend. When an operation changes state, any custom React component can subscribe to these updates independently of the global store. This guide shows you how to implement event listeners in nab138/iloader using the same patterns found in the main application.

Understanding the Event Architecture

iLoader's operation system broadcasts typed events that flow from the backend to the frontend through a dedicated channel.

The OperationUpdate Type

The OperationUpdate interface is defined in [src/components/operations.ts](/blob/main/src/components/operations.ts). This type describes the structure of every event payload:

interface OperationUpdate {
  updateType: "started" | "finished" | "failed";
  stepId: string;
  extraDetails?: AppError; // Present only when updateType is "failed"
}

The stepId identifies specific phases like download, install, or pair. The updateType indicates the lifecycle stage, while extraDetails contains error information when operations fail.

Global Listener in App.tsx

The main application registers a global listener in [src/App.tsx](/blob/main/src/App.tsx) to capture all operation events:

import { listen } from "@tauri-apps/api/event";
import type { OperationUpdate } from "./components/operations";

useEffect(() => {
  const unlisten = await listen<OperationUpdate>("operation", (e) => {
    // Global state update logic
  });
  return () => unlisten();
}, []);

This establishes the "operation" event channel that custom components can tap into.

Subscribing to Operation Events

Any React component can subscribe directly to operation events without modifying the global store.

Basic Listener Implementation

Import listen from @tauri-apps/api/event and the OperationUpdate type. Register the listener inside a useEffect hook:

import { useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import type { OperationUpdate } from "../components/operations";

export const useOperationListener = () => {
  const [lastUpdate, setLastUpdate] = useState<OperationUpdate | null>(null);

  useEffect(() => {
    let unlistenFn: (() => void) | undefined;

    const setupListener = async () => {
      unlistenFn = await listen<OperationUpdate>("operation", (event) => {
        setLastUpdate(event.payload);
      });
    };

    setupListener();

    return () => {
      unlistenFn?.();
    };
  }, []);

  return lastUpdate;
};

The cleanup function invokes the unlisten callback to prevent memory leaks when the component unmounts.

Handling Payload Variations

Operation events emit three distinct update types. Discriminate based on the updateType field:

await listen<OperationUpdate>("operation", (event) => {
  const { updateType, stepId, extraDetails } = event.payload;
  
  switch (updateType) {
    case "started":
      console.log(`Step ${stepId} began`);
      break;
    case "finished":
      console.log(`Step ${stepId} completed`);
      break;
    case "failed":
      console.error(`Step ${stepId} failed:`, extraDetails);
      break;
  }
});

Integration with Global State

While direct subscription works for isolated components, most applications should consume operation state through the centralized store provided by [src/StoreContext.tsx](/blob/main/src/StoreContext.tsx).

Access the store using the context hook:

import { useStore } from "../StoreContext";

const Component = () => {
  const { operations } = useStore();
  // Access current operation state without manual event handling
};

Complete Example: OperationWatcher Component

Here is a reusable component that displays the most recent operation update, following the pattern used in [src/components/OperationView.tsx](/blob/main/src/components/OperationView.tsx):

import { useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import type { OperationUpdate } from "../components/operations";

export const OperationWatcher = () => {
  const [lastUpdate, setLastUpdate] = useState<OperationUpdate | null>(null);

  useEffect(() => {
    let unlisten: (() => void) | undefined;

    const startListening = async () => {
      unlisten = await listen<OperationUpdate>("operation", (event) => {
        console.log("Received operation update:", event.payload);
        setLastUpdate(event.payload);
      });
    };

    startListening();

    return () => {
      unlisten?.();
    };
  }, []);

  return (
    <div className="operation-monitor">
      <h3>Current Operation Status</h3>
      {lastUpdate ? (
        <div>
          <p>Step: {lastUpdate.stepId}</p>
          <p>Status: {lastUpdate.updateType}</p>
          {lastUpdate.extraDetails && (
            <pre>{JSON.stringify(lastUpdate.extraDetails, null, 2)}</pre>
          )}
        </div>
      ) : (
        <p>Waiting for operation events...</p>
      )}
    </div>
  );
};

Summary

  • Import listen from @tauri-apps/api/event to register operation event handlers in custom components.
  • Use OperationUpdate from [src/components/operations.ts](/blob/main/src/components/operations.ts) for TypeScript type safety when handling payloads.
  • Subscribe to the "operation" event channel to receive real-time updates on task lifecycle changes.
  • Always cleanup listeners by invoking the unsubscribe function returned by listen in your useEffect cleanup phase.
  • Prefer the global store in [src/StoreContext.tsx](/blob/main/src/StoreContext.tsx) for state shared across multiple components rather than duplicating listeners.

Frequently Asked Questions

What is the exact event name for operation updates in iLoader?

The event channel is named "operation". This string is passed as the first argument to Tauri's listen function, as implemented in [src/App.tsx](/blob/main/src/App.tsx). Listening to any other channel will not capture operation lifecycle events.

How do I properly type the operation event payload?

Import the OperationUpdate interface from [src/components/operations.ts](/blob/main/src/components/operations.ts) and pass it as the generic parameter to listen<OperationUpdate>(). This ensures compile-time checking for updateType, stepId, and extraDetails.

Should I subscribe to events directly or use the global store?

For isolated components like progress indicators, direct subscription is appropriate. For shared state accessed by multiple components, consume the store via [src/StoreContext.tsx](/blob/main/src/StoreContext.tsx) instead to avoid performance overhead from duplicate listeners.

What happens if I forget to cleanup the event listener?

Failing to call the unsubscribe function creates a memory leak. The listener retains references to your component's state after unmounting. Always return a cleanup function from useEffect that invokes the unlisten handler returned by listen.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →