Files
inter-hub/docs/react-adapter.md
Bernd Worsch 803376b09a feat(P6/T06): React adapter specification and reference example
ihf-react-adapter.js: useWidgetEnvelope (EnvelopeEmissionContract v1.0 props),
withWidgetEnvelope HOC, useInteractionReporter (POSTs to reporting contract endpoint).
Plain ESM module — no IHP build toolchain required. docs/react-adapter.md with
usage examples. static/ihf-react-test.html: React widget + native IHP widget on
same page demonstrating annotation launcher pickup and reportEvent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:19:26 +00:00

3.7 KiB

IHF React Adapter

static/js/ihf-react-adapter.js provides three exports that let React components participate in the IHF widget protocol:

Export What it does
useWidgetEnvelope Hook — returns data-* props for the envelope
withWidgetEnvelope HOC — wraps a component with the envelope div
useInteractionReporter Hook — returns reportEvent(type) for API submission

The adapter conforms to:

  • EnvelopeEmissionContract v1.0 — emits data-widget-id, data-view-context, data-hub-id
  • InteractionReportingContract v1.0 — POSTs to /api/v1/interaction-events with Bearer auth

No build toolchain is added to the IHP project. Consumers bundle this file themselves (webpack, vite, esbuild, etc.) or load it via <script type="module">.


useWidgetEnvelope

import { useWidgetEnvelope } from '/js/ihf-react-adapter.js';

function MetricsPanel({ widgetId, hubId }) {
  const { ref, envelopeProps } = useWidgetEnvelope(
    widgetId,
    hubId,
    'ops-dashboard',              // viewContext
    { policyScope: 'internal' }   // optional
  );

  return (
    <div ref={ref} {...envelopeProps} className="p-4 border rounded">
      <h2>Metrics</h2>
      {/* content */}
    </div>
  );
}

The envelopeProps object contains all required data-* attributes. The ref allows the annotation launcher to find the element.


withWidgetEnvelope

import { withWidgetEnvelope } from '/js/ihf-react-adapter.js';

// Wrap at module definition time, supplying stable IDs:
const GovernedMetricsPanel = withWidgetEnvelope(
  MetricsPanel,
  'a3f1c2b4-...',   // widgetId (UUID from widget registry)
  'b9e2d3a1-...',   // hubId
  'ops-dashboard'   // viewContext
);

// Use anywhere:
function Dashboard() {
  return <GovernedMetricsPanel title="CPU Usage" />;
}

The HOC wraps the component in an envelope <div> carrying the data-* attributes. The inner component receives its props unchanged.


useInteractionReporter

import { useInteractionReporter } from '/js/ihf-react-adapter.js';

function ActionButton({ widgetId, hubId, apiKey }) {
  const { reportEvent } = useInteractionReporter(widgetId, hubId, apiKey);

  async function handleClick() {
    await reportEvent('clicked');
    // proceed with action
  }

  return <button onClick={handleClick}>Run Report</button>;
}

Accepted event types (InteractionReportingContract v1.0): clicked, viewed, submitted, dismissed, errored

The apiKey is the hub's bearer token (hubs.api_key). Retrieve it from your app's server-side config — do not embed it in public bundle output.


Combined example

import {
  useWidgetEnvelope,
  useInteractionReporter,
} from '/js/ihf-react-adapter.js';

function GovernedButton({ widgetId, hubId, apiKey, label, onAction }) {
  const { envelopeProps } = useWidgetEnvelope(widgetId, hubId, 'action-bar');
  const { reportEvent }   = useInteractionReporter(widgetId, hubId, apiKey);

  async function handleClick() {
    await reportEvent('clicked');
    onAction();
  }

  return (
    <div {...envelopeProps}>
      <button onClick={handleClick}>{label}</button>
    </div>
  );
}

The annotation launcher (ihf-annotation-launcher.js) will automatically detect the data-widget-id attribute and inject an Annotate trigger alongside this component if IHP_ANNOTATION_LAUNCHER=true.


Test fixture

static/ihf-react-test.html demonstrates a React widget alongside an IHP-rendered widget on the same page. Open it directly in the browser (no server needed for the React side).