> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React hooks

> Use the TypeScript Bridge SDK with React via context provider and hooks

Complete guide for using `@gbgplc-internal/ggo-native-bridge-react` in React applications.

## Setup

Wrap your app (or the relevant subtree) with `<BridgeProvider>`:

```tsx theme={null}
import { BridgeProvider } from '@gbgplc-internal/ggo-native-bridge-react';

function App() {
  return (
    <BridgeProvider>
      <YourApp />
    </BridgeProvider>
  );
}
```

The provider creates the `NativeBridge` singleton, auto-detects the host environment, discovers capabilities on mount, and makes the bridge available to all child components via context.

## `useBridge()` — Access the bridge

`useBridge()` is the main hook for reading bridge state and reaching the underlying `NativeBridge` instance. It returns the environment, capability map, and helpers for capability checks:

```tsx theme={null}
import { useBridge } from '@gbgplc-internal/ggo-native-bridge-react';

function MyComponent() {
  const {
    bridge,             // NativeBridge instance
    environment,        // 'ios' | 'android' | 'iframe' | 'standalone'
    isEmbedded,         // true if inside a host
    capabilities,       // CapabilityMap
    capabilitiesReady,  // true once discovery completes
    hasCapability,      // (id: string) => boolean
  } = useBridge();

  bridge.emit('journey.started', { timestamp: Date.now() });
}
```

Throws an `Error` if used outside a `<BridgeProvider>`.

### Waiting for capabilities

```tsx theme={null}
function CapabilityGate({ children }: { children: React.ReactNode }) {
  const { capabilitiesReady } = useBridge();

  if (!capabilitiesReady) return <LoadingSpinner />;
  return <>{children}</>;
}
```

## `useCapability(id)` — Check a capability

Reactively check whether a specific capability is supported:

```tsx theme={null}
import { useCapability } from '@gbgplc-internal/ggo-native-bridge-react';
import { CAPABILITY_IDS } from '@gbgplc-internal/ggo-native-bridge';

function NfcCard() {
  const { supported, ready, version } = useCapability(CAPABILITY_IDS.NFC_READ);

  if (!ready) return <p>Checking NFC support...</p>;

  if (!supported) {
    return <p>NFC is not available on this device.</p>;
  }

  return (
    <div>
      <p>NFC ready (v{version})</p>
      <button>Scan Passport</button>
    </div>
  );
}
```

## `useNativeCamera(type)` — Camera capture

`useNativeCamera()` is a convenience hook that wraps the most common bridge request — a document or selfie capture — with the right capability check, action string, and timeout. It returns a flag for whether a native capture is available and a `requestCapture()` function you call when the user triggers capture:

```tsx theme={null}
import { useNativeCamera } from '@gbgplc-internal/ggo-native-bridge-react';
import { BridgeError, BridgeErrorCode } from '@gbgplc-internal/ggo-native-bridge';

function DocumentCapture() {
  const { isNativeAvailable, requestCapture } = useNativeCamera('document');

  async function handleCapture() {
    if (!isNativeAvailable) {
      openWebCamera();
      return;
    }

    try {
      const response = await requestCapture({
        documentType: 'passport',
        side: 'front',
      });
      processImage(response.data);
    } catch (error) {
      if (error instanceof BridgeError) {
        if (error.code === BridgeErrorCode.CANCELLED) return;
        if (error.recoverable) {
          showRetryPrompt();
          return;
        }
      }
      showErrorMessage('Capture failed');
    }
  }

  return (
    <button onClick={handleCapture}>
      {isNativeAvailable ? 'Capture with Native Camera' : 'Capture with Web Camera'}
    </button>
  );
}
```

### Camera type mapping

Each `type` value maps to a specific bridge capability and capture action:

| `type`       | Capability checked | Action sent               |
| ------------ | ------------------ | ------------------------- |
| `'document'` | `camera.document`  | `camera.document.capture` |
| `'selfie'`   | `camera.selfie`    | `camera.selfie.capture`   |

`useNativeCamera` works even without a `<BridgeProvider>`, returning `isNativeAvailable: false`. This makes it safe to use in components that may or may not have bridge access.

## `useBridgeEvent(action, callback)` — Listen for host events

```tsx theme={null}
import { useBridgeEvent } from '@gbgplc-internal/ggo-native-bridge-react';

function HostEventListener() {
  useBridgeEvent('host.navigation.back', (data) => {
    navigateBack();
  });

  useBridgeEvent('host.theme.update', (data) => {
    const theme = data.theme as Record<string, string>;
    for (const [key, value] of Object.entries(theme)) {
      document.documentElement.style.setProperty(key, value);
    }
  });

  return null;
}
```

Key behaviours:

* Subscribes on mount, unsubscribes on unmount
* You do **not** need to wrap the callback in `useCallback` — the hook uses a ref internally
* Resubscribes only if the `action` string changes

## Emitting events

```tsx theme={null}
function JourneyTracker() {
  const { bridge } = useBridge();

  useEffect(() => {
    bridge.emit('journey.started', { journeyId: 'abc-123', timestamp: Date.now() });
    return () => {
      bridge.emit('journey.completed', { journeyId: 'abc-123', timestamp: Date.now() });
    };
  }, []);

  return null;
}
```

## Combining hooks

```tsx theme={null}
function VerificationStep() {
  const { environment, hasCapability, bridge } = useBridge();
  const { supported: hasDocCamera, ready } = useCapability('camera.document');
  const { isNativeAvailable, requestCapture } = useNativeCamera('document');

  useBridgeEvent('host.navigation.back', () => goToPreviousStep());

  if (!ready) return <LoadingSpinner />;

  return (
    <div>
      <p>Running in: {environment}</p>
      {isNativeAvailable ? (
        <button onClick={() => requestCapture({ side: 'front' })}>
          Capture with Native Camera
        </button>
      ) : (
        <WebCamera />
      )}
    </div>
  );
}
```

## Testing

Use the `environment` and `channel` options to mock the bridge in tests:

```tsx theme={null}
import { render, screen } from '@testing-library/react';
import { BridgeProvider } from '@gbgplc-internal/ggo-native-bridge-react';
import { NativeBridge } from '@gbgplc-internal/ggo-native-bridge';
import type { BridgeMessage, MessageChannel } from '@gbgplc-internal/ggo-native-bridge';

function createMockChannel() {
  let onMsg: ((msg: BridgeMessage) => void) | null = null;
  const sent: BridgeMessage[] = [];

  return {
    channel: {
      send(msg: BridgeMessage) { sent.push(msg); },
      onMessage(cb: (msg: BridgeMessage) => void) { onMsg = cb; },
      destroy() { onMsg = null; },
    } satisfies MessageChannel,
    sent,
    simulateResponse(msg: BridgeMessage) { onMsg?.(msg); },
  };
}

it('shows native camera button when capability exists', async () => {
  const mock = createMockChannel();

  render(
    <BridgeProvider options={{ environment: 'ios', channel: mock.channel }}>
      <DocumentCapture />
    </BridgeProvider>,
  );

  mock.simulateResponse({
    version: '1.0',
    correlationId: mock.sent[0].correlationId,
    type: 'response',
    timestamp: Date.now(),
    payload: {
      action: 'capability.query',
      status: 'success',
      data: {
        capabilities: {
          'camera.document': { supported: true, version: '2.0' },
        },
      },
    },
  });

  expect(await screen.findByText('Capture with Native Camera')).toBeTruthy();
});

afterEach(() => {
  NativeBridge.resetInstance();
});
```
