◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-07-13Advanced

postMessage Is Fire-and-Forget — Designing a Correlated Request/Response Bridge Between a WebView and React Native

postMessage between a WebView and React Native is one-way, so you never learn whether the work you asked for actually succeeded. Here is a typed request/response bridge, with correlation IDs and timeouts, built in working TypeScript.

WebViewReact Native239Rork585BridgeTypeScript8

✦ Premium Article

I could not tell whether the save had really finished

I had built a small app in Rork, as an indie developer, and embedded an existing web-based markdown editor inside a WebView. When the native "Save" button was pressed, injectedJavaScript pulled the editor's content and sent it to my own backend. That part worked within an hour.

The trouble showed up when a user closed the screen right after tapping Save. The content had not been retrieved yet, but a "saved" toast still appeared. On slow devices, the opposite happened and the same content was submitted twice. Tracing it, I realized the cause was a single thing: window.ReactNativeWebView.postMessage is a one-way channel, and there is no built-in way to know whether the work you asked for actually succeeded.

This article shares the request/response bridge I built to close that gap — one that ties each send to its reply with a correlation ID, so on the native side you can simply write await bridge.call(...).

Why postMessage is fire-and-forget

WebView communication in React Native looks bidirectional, but it is really two independent one-way pipes. From native to web, you push a JavaScript string with injectedJavaScript or webviewRef.injectJavaScript(). From web to native, you call window.ReactNativeWebView.postMessage(string) and receive it in the native onMessage.

Those two pipes have nothing that links a request to its reply. When native injects "give me the content," the framework does not tell you which of the messages that later arrive at onMessage corresponds to that request. With a single message type this is fine. But once you add save, fetch, validate, and "where is the scroll position" as distinct operations, you can no longer tell which reply belongs to which request, and state gets tangled.

An HTTP request and its response line up because a TCP connection or an HTTP/2 stream ID binds them. postMessage has none of that. So give it one yourself — a correlation ID. That was the starting point of the design.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A design that turns one-way postMessage into an awaitable call using correlation IDs
✦Cleaning up every request left dangling by a 5s timeout, unmounts, and reloads
✦Origin checks and a type allowlist so an embedded web page is never trusted blindly
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.
⬡ Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
⬡ Dev Tools2026-08-14
Find the native edits expo prebuild will erase before you upgrade to SDK 57
Expo SDK 57 makes expo prebuild clear and regenerate ios and android by default. Here is how to audit your hand edits first, move them into config plugins, and why 57.0.9 matters for Reanimated apps.