Fox Events Fox Events

Gerenciando o bridge e o adapter

Esta página descreve várias formas de gerenciar o bridge no lado WebView e o createFoxRNAdapter no lado React Native. Escolha o padrão que melhor se encaixa no ciclo de vida da sua aplicação.

---

Índice

---

1. Lado Web — Bridge

createReactNativeBridge(options) retorna uma função dispose() que remove o listener de message e o middleware de emit. Você pode usar o bridge de três formas.

1.1 Bridge global (uma vez, sem cleanup)

Inicialize uma vez em um módulo importado no entry point. O bridge fica ativo durante toda a vida da aplicação. Ideal quando a WebView não é desmontada ou quando você não precisa liberar recursos.

Módulo src/bridge.ts:

ts
import { createReactNativeBridge } from "fox-events/bridge-react-native";

createReactNativeBridge({
  direction: "both",
  filter: (name) => name.startsWith("app:"),
  debug: false,
});

Entry point (main.tsx): importe antes do App:

ts
import "./bridge";
import App from "./App";

Qualquer componente pode usar Fox.emit e Fox.on sem se preocupar com setup ou cleanup.

---

1.2 Bridge global com dispose no unmount

Mesma ideia de “bridge uma vez”, mas você exporta o dispose e chama no cleanup do componente raiz. Assim, quando a árvore React desmontar (ex.: usuário sai da tela que contém a WebView), o bridge é desmontado e não fica ouvindo message nem enviando para o RN.

Módulo src/bridge.ts:

ts
import { createReactNativeBridge } from "fox-events/bridge-react-native";

/**
 * Inicializa o bridge RN ↔ WebView uma vez.
 * Para desmontar (ex.: quando o componente raiz desmontar), chame disposeBridge()
 * no cleanup do useEffect: return () => disposeBridge();
 */
export const disposeBridge = createReactNativeBridge({
  direction: "both",
  filter: (name) => name.startsWith("app:"),
  debug: false,
});

Entry point: importe o módulo antes do App (o bridge já sobe na carga).

No componente raiz (ex.: App.tsx):

ts
import { useEffect } from "react";
import { disposeBridge } from "./bridge";

function App() {
  useEffect(() => {
    // ... seus Fox.on, etc. ...

    return () => disposeBridge();
  }, []);

  return (/* ... */);
}

Quando o componente desmontar, disposeBridge() remove o listener e o middleware. Use este padrão quando a WebView pode ser desmontada e você quer evitar listeners órfãos.

---

1.3 Bridge por componente (dentro do useEffect)

Crie e destrua o bridge por componente: no useEffect você chama createReactNativeBridge e no cleanup chama o dispose retornado. O bridge só existe enquanto o componente estiver montado.

ts
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 quando o bridge estiver amarrado à vida de um único componente (ex.: uma tela que monta/desmonta a WebView) e você não quiser um bridge global.

---

2. Lado RN — Adapter

createFoxRNAdapter({ sendToWebView, filter? }) retorna um adapter que você usa com fox.emit, fox.on, fox.handleMessage. Abaixo, quatro formas de gerenciar esse adapter.

2.1 Adapter no componente da WebView

Crie o adapter com useMemo no mesmo componente que renderiza a WebView e passe a ref para sendToWebView. O adapter vive apenas nesse componente; para usar em filhos, passe fox por props ou por Context.

ts
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)}
    />
  );
}

Ideal quando só esse componente (e talvez filhos via props) precisa do fox.

---

2.2 Adapter global (setFox / getFox)

Registre o adapter em um módulo global após criá-lo (ex.: no layout que tem a WebView). Em qualquer tela ou módulo, obtenha o adapter com getFox() e use fox.emit / fox.on sem Provider.

Módulo app/fox-global.ts:

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;
}

No layout/componente que tem a WebView: crie o adapter com useMemo e registre com setFox no useEffect:

ts
const fox = useMemo(
  () =>
    createFoxRNAdapter({
      sendToWebView: (msg) => {
        const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
        webViewRef.current?.injectJavaScript(code);
      },
    }),
  []
);

useEffect(() => {
  setFox(fox);
}, [fox]);

Em qualquer outra tela ou módulo:

ts
import { getFox } from "@/app/fox-global";

function OutraTela() {
  const fox = getFox();
  const handleEnviar = () => fox.emit("app:action", { id: "1" });
  return (/* ... */);
}

Assim você usa o mesmo adapter em várias telas sem passar props nem Provider.

---

2.3 Hook useFox que usa o global

Encapsule o getFox() em um hook para manter a API consistente e evitar chamar getFox() em todo lugar.

app/fox-context.tsx (ou similar):

ts
import type { FoxRNAdapter } from "fox-events/bridge-react-native";
import { getFox } from "./fox-global";

export function useFox(): FoxRNAdapter {
  return getFox();
}

Uso: const fox = useFox(); em qualquer componente. Por baixo continua usando o global (getFox), então não precisa de Provider.

---

2.4 Provider (Context)

Se preferir injetar o adapter pela árvore React (evitando módulo global), crie um Context e forneça o adapter no layout que tem a WebView.

ts
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 quando quiser evitar estado global e manter o adapter apenas na subárvore que envolver com FoxProvider.

---

3. Resumo

LadoPadrãoQuando usar
WebBridge global (sem cleanup)WebView não desmonta ou não precisa liberar recursos.
WebBridge global + disposeBridge() no cleanupWebView pode desmontar; você quer remover listeners ao sair da tela.
WebBridge no useEffect (dispose no return)Bridge atrelado ao ciclo de vida de um único componente.
RNAdapter no componenteSó o componente da WebView (e filhos via props) usa o adapter.
RNGlobal (setFox / getFox)Várias telas usam o mesmo adapter sem props nem Provider.
RNHook useFox() (usando global)Mesmo que o global, com API const fox = useFox();.
RNProvider (Context)Preferir injeção pela árvore em vez de módulo global.

Para mais detalhes de configuração e exemplos de eventos, veja Bridge React Native.