Skip to content

Revise mocking setup instructions for React Native - #529

Open
coolsoftwaretyler wants to merge 5 commits into
mswjs:mainfrom
coolsoftwaretyler:patch-1
Open

Revise mocking setup instructions for React Native#529
coolsoftwaretyler wants to merge 5 commits into
mswjs:mainfrom
coolsoftwaretyler:patch-1

Conversation

@coolsoftwaretyler

@coolsoftwaretyler coolsoftwaretyler commented Jul 17, 2026

Copy link
Copy Markdown

Hey folks - love MSW. I recently used it on a large React Native project to some success and I'm trying to document some of the pain points of React Native integration (see this example PR integrating with Ignite: infinitered/mswtest#2).

I'm not exactly sure what the mechanism is, but if you await anything before calling registerRootComponent, Expo will fail to register the app entry. I have a demo of the issue here

I propose using some check at the top of the app tree, although another good approach might be to delay hiding the splash screen

Updated the instructions for enabling mocking in React Native applications to include initialization before rendering the app component.
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@coolsoftwaretyler is attempting to deploy a commit to the MSW Team on Vercel.

A member of the Team first needs to authorize it.

@coolsoftwaretyler coolsoftwaretyler left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self review to make a couple quick edits

Comment thread websites/mswjs.io/src/content/docs/integrations/react-native.mdx Outdated
Comment thread websites/mswjs.io/src/content/docs/integrations/react-native.mdx Outdated
Comment thread websites/mswjs.io/src/content/docs/integrations/react-native.mdx Outdated
@kettanaito

Copy link
Copy Markdown
Member

Thanks @coolsoftwaretyler. React Native integration certainly needs some love but all the pieces to make it work should already be there.

Regarding no await before registering the root, you mentioned async().then(register) would work. Can we please use that approach as it allows us to separate enableMocking() from the root component registration more cleanly? This is also the pattern we use and showcase for other integrations. As a rule of thumb, MSW's registration state shouldn't be embedded into the UI framework state.

@coolsoftwaretyler

Copy link
Copy Markdown
Author

Hey @kettanaito - that still doesn't work, unfortunately.

Any await in the chain breaks initialization:

CleanShot.2026-07-20.at.09.50.54.mp4

I'll see if I can either figure out what's going on with registerRootComponent or perhaps find some alternate way to initialize MSW outside of the UI code. But right now that's the only set up I know of that works in React Native.

@coolsoftwaretyler

Copy link
Copy Markdown
Author

Hey @kettanaito - I am fairly positive we can't reliably await before registering a root component. React Native calls runApplication from its JavaScript runtime at startup, which checks to see if we have registered anything yet. This happens synchronously, sometime before the promise resolves (I presume because the await line ends up getting enqueued in Hermes' microtasks, so it doesn't process by the time the AppRegisteryBinding is calling runApplication

I think MSW/React Native directions may need one of two things:

  1. A slightly adjusted MSW documentation where we concede that there's reason to interface with the UI code for this (maybe we could update the instructions to use SplashScreen hiding as a mechanism)
  2. Some kind of change in React Native's initialization internals to support this kind of sequence

I think updating the docs here is an easier path forward, but I'll keep trying a few things to see if I can iron it out.

@kettanaito

Copy link
Copy Markdown
Member

@coolsoftwaretyler, what if we keep the promise but drop await? Could you please try this?

function enableMocking() {
  import('msw/node').then(({ setupServer }) => {
    const server = setupServer()
    server.use(...handlers)
    server.listen()
  })
}

enableMocking().then(() => registerRootComponent(App))

Almost any promise-based compromise would be better than asking people to embed MSW into their component state.

@coolsoftwaretyler

Copy link
Copy Markdown
Author

@kettanaito - that works, but it seems to require disabling lazy loading in Metro. I have to do:

EXPO_NO_METRO_LAZY=true pnpm start

Otherwise I get errors like:

iOS Bundled 4ms node_modules/msw/lib/native/index.mjs (1 module)
 ERROR  [Error: Requiring unknown module "2043". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`.] 

Call Stack
  importAll (node_modules/expo/src/async-require/asyncRequireModule.ts:70:73)
  tryCallOne (address at (InternalBytecode.js:1:1296)
  anonymous (address at (InternalBytecode.js:1:4984)
 ERROR  [Error: Uncaught (in promise, id: 0) TypeError: Cannot set property 'importedAll' of undefined] 

Call Stack
  importAll (node_modules/expo/src/async-require/asyncRequireModule.ts:70:73)
  tryCallOne (address at (InternalBytecode.js:1:1296)
  anonymous (address at (InternalBytecode.js:1:4984)

I think the issue is that import() does not get included in the bundle in dev when lazy loading is true

This leaves us with two options:

  1. We could just import everything at the module scope. But that will bring in MSW polyfills and setup even outside of DEV environments
  2. It looks like require satisfies the blocking requirement and the bundling. This works and should allow us to exclude MSW code from production builds from what I can tell:
import "@expo/metro-runtime" // this is for fast refresh on web w/o expo-router
import { registerRootComponent } from "expo"

import { App } from "@/app"

async function enableMocking() {
  if (!__DEV__) return
  
  require("./msw.polyfills")
  const { setupServer } = require("msw/native")
  const { handlers } = require("./app/mocks/handlers")
  const server = setupServer(...handlers)
  server.listen()
}

enableMocking().then(() => registerRootComponent(App))

Updated the React Native integration documentation to improve clarity on enabling mocking and polyfills.
Comment on lines +40 to +66
import "web-streams-polyfill/dist/polyfill"

if (typeof globalThis.MessageEvent === "undefined") {
globalThis.MessageEvent = class MessageEvent {
constructor(type, init) {
this.type = type
this.data = init?.data ?? null
this.origin = init?.origin ?? ""
this.lastEventId = init?.lastEventId ?? ""
this.source = init?.source ?? null
this.ports = init?.ports ?? []
}
}
}

if (typeof globalThis.BroadcastChannel === "undefined") {
globalThis.BroadcastChannel = class BroadcastChannel {
constructor(_name) {}
postMessage() {}
close() {}
addEventListener() {}
removeEventListener() {}
dispatchEvent() {
return true
}
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: leftover requirements from mswjs/msw#2367 (comment), which has been partially resolved

## Setup

Import the `setupServer` function from `msw/native` and call it, providing your request handlers as the argument.
## Enable mocking

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I collapsed this section down for a simpler code example but we could always expand it

@coolsoftwaretyler

Copy link
Copy Markdown
Author

@kettanaito - just got back from a work trip and incorporated some of this feedback. These docs updates are based on an example PR I have in an Ignite app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants