Skip to content
Star

Using it in React ​

This is the web app running inside the WebView (React). The web side sends requests/commands and subscribes to events pushed by native.

You need the same contract ​

A React Native host imports the same contract. Swift and Kotlin hosts use native code generated from that contract. Regenerate native files whenever message names or types change.

Install ​

bash
npm install webview-bridge-kit
# react (>=18) already exists in your web app, no separate install

createBridgeClient ​

Call createBridgeClient from webview-bridge-kit/react once per app to create typed Provider/hooks.

ts
// app/bridge.ts
import { createBridgeClient } from 'webview-bridge-kit/react';
import { contract } from '@/shared/bridge-contract';

export const { BridgeProvider, useBridge, useBridgeEvent } = createBridgeClient(contract, {
  logger: import.meta.env.DEV ? console : undefined,
  defaultOptions: {
    request: { timeout: 3_000 },
  },
});

Options ​

The second argument to createBridgeClient(contract, options).

OptionTypeDescription
defaultOptions.request.timeoutnumber | 'none'Default request timeout (ms). Defaults to 30_000. 'none' disables it.
loggerPartial<Console>Records dropped messages (debug/info/warn/error). Optional.
transportTransportCustom transport for unusual WebViews. Not needed for standard RN/iOS/Android hosts.

contract and options are frozen to their values at call time. (That's why the Provider takes no options prop — to avoid the options object changing during render.)

Installing the Provider ​

Wrap BridgeProvider once at the top of your app. It creates the web bridge internally and disposes it on unmount.

tsx
// App.tsx
import { BridgeProvider } from '@/app/bridge';

export function App() {
  return (
    <BridgeProvider>
      <AppRoutes />
    </BridgeProvider>
  );
}

Sending request / command ​

Get the bridge in any child component with useBridge.

tsx
import { useBridge } from '@/app/bridge';

export function LoginButton() {
  const bridge = useBridge();

  const onClick = async () => {
    // request — await the reply
    const { accessToken } = await bridge.request('KAKAO_LOGIN', undefined, { timeout: 'none' });
    await loginToServer(accessToken);
  };

  return <button onClick={onClick}>Kakao login</button>;
}
ts
// request with a payload
const { token } = await bridge.request('GET_FCM_TOKEN');

// command — fire-and-forget, no await
bridge.send('OPEN_INSTAGRAM', { username: 'peelie' });
bridge.send('OPEN_CAMERA');

Omit the argument when the payload is void. Passing { timeout } at call time overrides the contract/instance default.

Receiving events (useBridgeEvent) ​

Subscribe to events pushed by native with useBridgeEvent. It subscribes on mount, unsubscribes on unmount, and always calls the latest render's handler — so no stale-closure worries.

tsx
import { useBridgeEvent } from '@/app/bridge';

export function PhotoWatcher() {
  useBridgeEvent('PHOTO_TAKEN', ({ uri }) => {
    upload(uri);
  });

  return null;
}

You only receive events that arrive after you subscribe

Events emitted by native are not buffered. An event sent before the subscribing component mounts is lost.