How to Register a Device-List Refresh from Outside the Device.tsx Component

The Device component exposes a registerRefresh callback prop that allows parent components to capture and invoke its internal loadDevices function from anywhere in the React application.

In the nab138/iloader repository, decoupling device-list updates from the UI component that renders them requires a specific pattern to expose internal refresh capabilities. The codebase implements a callback registration system that lets external components trigger device list reloads without tight coupling or excessive prop drilling. This article examines how the registerRefresh prop in Device.tsx enables parent components like App.tsx to store and invoke the loadDevices method from anywhere in the component tree.

Understanding the registerRefresh Pattern in Device.tsx

The Device component defines an optional registerRefresh prop that accepts a function capable of receiving the internal loadDevices method. According to the source code in src/Device.tsx, the prop type signature is:

registerRefresh?: (fn?: () => void) => void

Inside the component, a useEffect hook registers the refresh handler on mount and cleans it up on unmount (lines 62-66):

useEffect(() => {
  registerRefresh?.(loadDevices);
  return () => registerRefresh?.(undefined);
}, [registerRefresh, loadDevices]);

This pattern allows the parent to capture a stable reference to loadDevices while ensuring the callback is cleared when the component unmounts, preventing memory leaks or stale closure issues.

Capturing the Refresh Function in App.tsx

The top-level App.tsx implements the consumer side of this pattern by creating a mutable ref to store the refresh callback. As implemented in lines 44-48 and 94-99 of src/App.tsx, the component declares:

const refreshDevicesRef = useRef<(() => void) | null>(null);

This ref is then passed to the Device component via the registerRefresh prop:

<Device
  // ... other props
  registerRefresh={(fn) => {
    refreshDevicesRef.current = fn ?? null;
  }}
/>

The setter function updates the ref whenever Device mounts or the loadDevices function changes, ensuring refreshDevicesRef.current always points to the valid refresh implementation.

Triggering Refreshes from External Components

Once App.tsx holds the reference, any component with access to refreshDevicesRef can trigger a device-list reload. The iLoader application already utilizes this capability for the toolbar refresh button and the R keyboard shortcut (lines 46-53 of App.tsx).

To register a device-list refresh from outside Device.tsx in your own implementation:

Access the ref via React Context

Create a context provider in App.tsx to share refreshDevicesRef with the component tree:

// src/components/RefreshButton.tsx
import { useContext } from "react";
import { RefreshContext } from "../App"; // assume App provides a context

export const RefreshButton = () => {
  const refreshRef = useContext(RefreshContext);
  return (
    <button onClick={() => refreshRef.current?.()}>
      Refresh Devices
    </button>
  );
};

Implement a custom hook for reusable access

Abstract the ref access into a dedicated hook to avoid direct context imports throughout your codebase:

// src/hooks/useRefreshDevices.ts
import { useContext } from "react";
import { RefreshContext } from "../App";

export const useRefreshDevices = () => {
  const ref = useContext(RefreshContext);
  return () => ref.current?.();
};

Trigger refreshes from background processes

For non-UI components like polling services or WebSocket listeners, invoke the refresh function imperatively:

// src/components/ExternalRefresh.tsx
import { useEffect } from "react";
import { useRefreshDevices } from "../hooks/useRefreshDevices";

export const ExternalRefresh = () => {
  const refresh = useRefreshDevices();

  useEffect(() => {
    // Auto-refresh every 5 minutes
    const interval = setInterval(() => refresh(), 5 * 60_000);
    return () => clearInterval(interval);
  }, [refresh]);

  return null;
};

Summary

  • The registerRefresh prop in src/Device.tsx exposes the internal loadDevices function through a callback registration pattern (lines 62-66).
  • App.tsx stores the callback in a useRef container, making it accessible application-wide without prop drilling (lines 44-48, 94-99).
  • External triggers can call refreshDevicesRef.current?.() to force device-list updates from sibling components, background tasks, or keyboard shortcuts.
  • Cleanup is automatic—the useEffect return handler in Device.tsx clears the ref on unmount to prevent stale references.

Frequently Asked Questions

What is the type signature of the registerRefresh prop in Device.tsx?

The registerRefresh prop accepts an optional function with the type (fn?: () => void) => void. It receives the internal loadDevices function as an argument when the component mounts, or undefined when unmounting to facilitate cleanup.

How does App.tsx prevent memory leaks when using the refresh ref?

App.tsx passes a setter to registerRefresh that updates refreshDevicesRef.current directly. When Device.tsx unmounts, its useEffect cleanup function invokes registerRefresh with undefined, clearing the ref and preventing calls to a destroyed component's state updaters.

Can I trigger a refresh from a deeply nested component without prop drilling?

Yes. By exposing refreshDevicesRef through React Context or a custom hook, any component in the tree can access and invoke the refresh function without receiving props from App.tsx directly. The ref pattern eliminates the need to pass the callback through every intermediate component.

What happens if I call refreshDevicesRef.current before Device mounts?

The ref initializes to null, so calling refreshDevicesRef.current?.() before the Device component mounts safely returns undefined without throwing errors. Once Device mounts and the useEffect executes, the ref populates with the valid loadDevices function.

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 →