Msw: Improve documentation on browser integration with React

Created on 25 Aug 2020  路  9Comments  路  Source: mswjs/msw

The getting started guide at https://mswjs.io/docs/getting-started/integrate/browser (and various other places) suggests the following to integrate with a React app.

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/browser')
  worker.start()
}

ReactDOM.render(<App />, document.getElementById('root'))

I found that with a very basic React app based on create-react-app that the app had rendered and made API calls before the service worker had finished starting and so it was unable to intercept them.

After a fair amount of frustration trying to figure out the problem, I tried the following and it resolved the issue. Can I suggest that the getting started guide and other documentation is updated to include something similar?

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

function renderApp() {
  ReactDOM.render(<App />, document.getElementById('root'))
}

if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/browser')
  worker.start().then(() => renderApp())
} else {
  renderApp()
}
docs

Most helpful comment

You saved my day @mike-trout

I was struggling with this, I understood that I had a lag between the rendering and the worker but didn't understand exactly why my first requests weren't caught by msw.

I thought it was the idea of https://mswjs.io/docs/api/setup-worker/start#waituntilready but apparently no, or it doesn't work well...?

Well, thank you!

All 9 comments

You saved my day @mike-trout

I was struggling with this, I understood that I had a lag between the rendering and the worker but didn't understand exactly why my first requests weren't caught by msw.

I thought it was the idea of https://mswjs.io/docs/api/setup-worker/start#waituntilready but apparently no, or it doesn't work well...?

Well, thank you!

Hey, @mike-trout. Thanks for raising this.

This is rather odd, as we ship a behavior to capture and defer any outgoing requests in that time period between calling worker.start() and when the worker registration resolves.

https://github.com/mswjs/msw/blob/2943c080bbdaaf74ec9e23147592fe76a0e6a147/src/utils/deferNetworkRequestsUntil.ts#L8-L29

That being said, this issue is yet another motivation to go through the examples and ensure all of them have a request example on application mount. Our examples are also smoke tests, so we can track regressions, so it's the right place to add the scenario which you described.

@kud, yes, your understanding is correct: waitUntilReady captures all outgoing requests before the worker is ready, and executes them once it is.

May I ask you how do you perform a request in your scenarios? Do you use a certain request issuing library? I'd love to hear more details on this, as we had a few tests on initial mount request interception and they passed.

I don't mind adding such recipe (pull request: https://github.com/mswjs/mswjs.io/pull/67), but I'd like to learn more about your usage first. This is not something I'd recommend by default, as it goes against a non-intrusive principle of the library. Integrating mocks should not touch your actual code, and awaiting things in order to mount an app is the definition of touching the actual code. If we can solve this with waitUntilReady that would be the best. Looking forward to hearing more details from you, folks!

I had the same issue and @mike-trout 's work-around fixed it.
If you need a repo for reproducing this issue let me know. I have a project where this can be reproduced every time, but I'll need to check if the example can be minimized.

// This doesn't work. Most of the requests are captured but not all
const { worker } = require('./mocks/browser');
worker.start({ waitUntilReady: true }); // Should be true by default
renderApp();

// This works all the time. All requests are captured
const { worker } = require('./mocks/browser');
worker.start().then(() => renderApp());

@kettanaito, I've pushed my quick-and-dirty React + MSW + WDIO project to https://github.com/mike-trout/msw-learning-lunch. Hopefully you can recreate with npm run start:mock and npm run test:e2e. I consistently see the issue on macOS and Chrome 84.

I'm definitely no expert on promises, but I can't see how if worker.start() returns a promise that you can expect any of the internal function calls, e.g. deferNetworkRequestsUntil, to have completed without awaiting the returned promise? But it is pretty late so I may be missing some obvious.

To add some details, I use Firefox (Nightly / macOS), and I've got the same issue.

@mike-trout the deferNetworkRequestsUntil function is a side-effect, and it's not connected to the worker.start() promise. Calling worker.start() does return a Promise, but it also calls the network deferring function internally.

https://github.com/mswjs/msw/blob/69fcf5afd9d309b784cd4111142a23548fea0f0a/src/setupWorker/start/createStart.ts#L132-L137

This means that as soon as you call worker.start() it starts network deferring, regardless if the returned Promise has resolved yet. I've tested this on a few use cases and it worked, but apparently it wasn't sufficient, as people experience issues like this. I think a good place to start is to ensure that each example has an on-load request added and tested.

Documentation now has the "Deferred mounting" recipe.

This should be helpful as a reference to anyone who stumbles upon a similar issue. Thank you for raising this!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hauptrolle picture hauptrolle  路  4Comments

dashed picture dashed  路  3Comments

danielstreit picture danielstreit  路  3Comments

veronesecoms picture veronesecoms  路  3Comments

derekr picture derekr  路  3Comments