Managing the bridge and adapter
This page describes different ways to manage the bridge on the WebView side and createFoxRNAdapter on the React Native side. Choose the pattern that best fits your app’s lifecycle.
---
Table of contents
- 1. Web side - Bridge
- 1.1 Global bridge (once, no cleanup)
- 1.2 Global bridge with dispose on unmount
- 1.3 Bridge per component (inside useEffect)
- 2. RN side - Adapter
- 2.1 Adapter in the WebView component
- 2.2 Global adapter (setFox / getFox)
- 2.3 useFox hook using the global
- 2.4 Provider (Context)
- 3. Summary
---
1. Web side — Bridge
createReactNativeBridge(options) returns a dispose() function that removes the message listener and the emit middleware. You can use the bridge in three ways.
1.1 Global bridge (once, no cleanup)
Initialize once in a module imported from your entry point. The bridge stays active for the whole app lifetime. Best when the WebView is never unmounted or you don’t need to release resources.
Module src/bridge.ts:
import { createReactNativeBridge } from "fox-events/bridge-react-native";
createReactNativeBridge({
direction: "both",
filter: (name) => name.startsWith("app:"),
debug: false,
});Entry point (main.tsx): import before App:
import "./bridge";
import App from "./App";Any component can use Fox.emit and Fox.on without worrying about setup or cleanup.
---
1.2 Global bridge with dispose on unmount
Same idea as “bridge once”, but you export the dispose and call it in the root component’s cleanup. When the React tree unmounts (e.g. user leaves the screen that contains the WebView), the bridge is torn down and no longer listens for message or sends to RN.
Module src/bridge.ts:
import { createReactNativeBridge } from "fox-events/bridge-react-native";
/**
* Initialize the RN ↔ WebView bridge once.
* To tear it down (e.g. when the root component unmounts), call disposeBridge()
* in your useEffect cleanup: return () => disposeBridge();
*/
export const disposeBridge = createReactNativeBridge({
direction: "both",
filter: (name) => name.startsWith("app:"),
debug: false,
});Entry point: import this module before App (the bridge starts on load).
In the root component (e.g. App.tsx):
import { useEffect } from "react";
import { disposeBridge } from "./bridge";
function App() {
useEffect(() => {
// ... your Fox.on, etc. ...
return () => disposeBridge();
}, []);
return (/* ... */);
}When the component unmounts, disposeBridge() removes the listener and middleware. Use this when the WebView can unmount and you want to avoid orphan listeners.
---
1.3 Bridge per component (inside useEffect)
Create and destroy the bridge per component: in useEffect you call createReactNativeBridge, and in the cleanup you call the returned dispose. The bridge only exists while the component is mounted.
import { useEffect } from "react";
import { Fox } from "fox-events";
import { createReactNativeBridge } from "fox-events/bridge-react-native";
function WebViewScreen() {
useEffect(() => {
const dispose = createReactNativeBridge({
direction: "both",
filter: (name) => name.startsWith("app:"),
debug: false,
});
Fox.on("app:command", (payload) => {
/* ... */
});
return () => dispose();
}, []);
return (/* ... */);
}Use when the bridge is tied to a single component’s lifetime (e.g. a screen that mounts/unmounts the WebView) and you don’t want a global bridge.
---
2. RN side — Adapter
createFoxRNAdapter({ sendToWebView, filter? }) returns an adapter you use with fox.emit, fox.on, fox.handleMessage. Below are four ways to manage that adapter.
2.1 Adapter in the WebView component
Create the adapter with useMemo in the same component that renders the WebView, and pass the ref into sendToWebView. The adapter lives only in that component; to use it in children, pass fox via props or Context.
import { useMemo, useRef } from "react";
import { WebView } from "react-native-webview";
import { createFoxRNAdapter } from "fox-events/bridge-react-native";
function App() {
const webViewRef = useRef<WebView>(null);
const fox = useMemo(
() =>
createFoxRNAdapter({
sendToWebView: (msg) => {
const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
webViewRef.current?.injectJavaScript(code);
},
filter: (name) => name.startsWith("app:"),
}),
[]
);
return (
<WebView
ref={webViewRef}
source={{ uri: "https://example.com" }}
onMessage={(e) => fox.handleMessage(e.nativeEvent.data)}
/>
);
}Best when only this component (and maybe children via props) needs fox.
---
2.2 Global adapter (setFox / getFox)
Register the adapter in a global module after creating it (e.g. in the layout that has the WebView). From any screen or module, get the adapter with getFox() and use fox.emit / fox.on without a Provider.
Module app/fox-global.ts:
import type { FoxRNAdapter } from "fox-events/bridge-react-native";
let foxInstance: FoxRNAdapter | null = null;
export function setFox(fox: FoxRNAdapter): void {
foxInstance = fox;
}
export function getFox(): FoxRNAdapter {
if (foxInstance == null) {
throw new Error(
"Fox not initialized. Ensure the layout with WebView has mounted and called setFox(fox)."
);
}
return foxInstance;
}In the layout/component that has the WebView: create the adapter with useMemo and register it with setFox in useEffect:
const fox = useMemo(
() =>
createFoxRNAdapter({
sendToWebView: (msg) => {
const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
webViewRef.current?.injectJavaScript(code);
},
}),
[]
);
useEffect(() => {
setFox(fox);
}, [fox]);From any other screen or module:
import { getFox } from "@/app/fox-global";
function OtherScreen() {
const fox = getFox();
const handleSend = () => fox.emit("app:action", { id: "1" });
return (/* ... */);
}This way you reuse the same adapter across screens without passing props or using a Provider.
---
2.3 useFox hook using the global
Wrap getFox() in a hook so the API stays consistent and you don’t call getFox() everywhere.
app/fox-context.tsx (or similar):
import type { FoxRNAdapter } from "fox-events/bridge-react-native";
import { getFox } from "./fox-global";
export function useFox(): FoxRNAdapter {
return getFox();
}Usage: const fox = useFox(); in any component. Under the hood it still uses the global (getFox), so no Provider is required.
---
2.4 Provider (Context)
If you prefer injecting the adapter through the React tree (avoiding a global module), create a Context and provide the adapter in the layout that has the WebView.
import { createContext, useContext, useMemo, useRef } from "react";
import { WebView } from "react-native-webview";
import { createFoxRNAdapter } from "fox-events/bridge-react-native";
const FoxContext = createContext<ReturnType<typeof createFoxRNAdapter> | null>(null);
export function useFox() {
const fox = useContext(FoxContext);
if (fox == null) throw new Error("useFox must be used within FoxProvider");
return fox;
}
function App() {
const webViewRef = useRef<WebView>(null);
const fox = useMemo(
() =>
createFoxRNAdapter({
sendToWebView: (msg) => {
const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
webViewRef.current?.injectJavaScript(code);
},
}),
[]
);
return (
<FoxContext.Provider value={fox}>
<WebView
ref={webViewRef}
onMessage={(e) => fox.handleMessage(e.nativeEvent.data)}
source={{ uri: "https://example.com" }}
/>
</FoxContext.Provider>
);
}Use when you want to avoid global state and keep the adapter only in the subtree wrapped with FoxProvider.
---
3. Summary
| Side | Pattern | When to use |
|---|---|---|
| Web | Global bridge (no cleanup) | WebView never unmounts or you don’t need to release resources. |
| Web | Global bridge + disposeBridge() in cleanup | WebView can unmount; you want to remove listeners when leaving the screen. |
| Web | Bridge in useEffect (dispose in return) | Bridge tied to a single component’s lifecycle. |
| RN | Adapter in component | Only the WebView component (and children via props) uses the adapter. |
| RN | Global (setFox / getFox) | Multiple screens use the same adapter without props or Provider. |
| RN | useFox() hook (using global) | Same as global, with a const fox = useFox(); API. |
| RN | Provider (Context) | Prefer injection through the tree instead of a global module. |
For setup details and event examples, see Bridge React Native.