Apollo-client: Inside StrictMode useSubscription subscribes twice

Created on 9 Mar 2020  ·  29Comments  ·  Source: apollographql/apollo-client

Intended outcome:

useSubscription should subscribe only once when run inside <StrictMode>

Actual outcome:

useSubscription subscribes twice to the same subscription. I think that this happens because <StrictMode> intentionally runs component bodies twice. I suppose that same thing could happen in concurrent mode too.

This results in double network traffic. Also, the second subscription won't get unsubscribed when the useSubscription is unmounted.

This can be inspected on chrome network tab:
usesubscription_chrome
in the picture, id 15 and 16 are the single useSubscription and receive the same data. After unmount the id 16 was closed and id 15 was left receiving data(not pictured).

I am not familiar with apollo-client codebase, but I think that this is result of the following line being run in function body instead of inside useEffect:
https://github.com/apollographql/apollo-client/blob/23b17fa7283b21966d1c633c52b848f12b4a211e/src/react/hooks/useSubscription.ts#L44

How to reproduce the issue:

Any subscription should behave the same when run in StrictMode.

const SubComponent = ({vesselId}) => {
  const { data } = useSubscription(vesselStatus, {
    variables: { vesselId }
  });
  return null;
}
<StrictMode>
  <SubComponent vesselId="245" />
</StrictMode>

Workaround is to not use StrictMode, but I'm afraid that will result in code that breaks when concurrent mode lands in React.

Versions

System:
OS: macOS 10.15.3
Binaries:
Node: 12.16.1 - ~/.nvm/versions/node/v12.16.1/bin/node
Yarn: 1.22.0 - ~/.nvm/versions/node/v12.16.1/bin/yarn
npm: 6.13.4 - ~/.nvm/versions/node/v12.16.1/bin/npm
Browsers:
Chrome: 80.0.3987.132
Firefox: 73.0.1
Safari: 13.0.5
npmPackages:
@apollo/client: ^3.0.0-beta.39 => 3.0.0-beta.39
@apollo/link-ws: ^2.0.0-beta.3 => 2.0.0-beta.3
apollo-server-express: ^2.11.0 => 2.11.0
npmGlobalPackages:
apollo-codegen: 0.20.2
apollo: 2.24.0

Most helpful comment

I agree, this is annoying. In my case I listen for events and display an alert for each of them in the UI which turns out to be duplicated. However the problem does not come from Apollo-Client, it is React strict mode behavior.. I don't think we can expect a fix coming from Apollo to remove this behavior since it is external to Apollo-Client.

There is a reason for strict mode rendering twice. It is to prevent bugs in the coming concurrent mode. So this should be fixed in apollo-client.

All 29 comments

If there is interest in fixing this, I can try to make a PR of a failing test case? Not sure it can be done cleanly though.

I'm having the same issue. <StrictMode /> e.g. seems to be enabled for create-react-app by default and causes the useSubscription hook to subscribe twice on initial render.

Update: strict mode also causes trouble when setting a pollInterval in useQuery which then keeps polling even if the component is unmounted.

Update: strict mode also causes trouble when setting a pollInterval in useQuery which then keeps polling even if the component is unmounted.

Both of these hooks will then fail under concurrent mode.

Yep, running into this issue as well - all of my useSubscription's are subscribing twice.

Experiencing the same issue. Any news?

Confirmed

We have the same problem. Any updates?

I have just started playing with Subscriptions. So I have a fresh CRA app here, and it is duplicating subscriptions. When I remove StrictMode, it stops.

Same issue for me

Notice that this is true only if you're in development mode.
Try to run a production build and this effect will go away.

Same thing for me. Wasted a couple of hours. I thought that I was going crazy.

Try to run a production build and this effect will go away.

That might hide the symptoms, but having to run a production build when doing active development would get tedious and annoying real fast. We need a real solution for this issue.

I agree, this is annoying. In my case I listen for events and display an alert for each of them in the UI which turns out to be duplicated. However the problem does not come from Apollo-Client, it is React strict mode behavior.. I don't think we can expect a fix coming from Apollo to remove this behavior since it is external to Apollo-Client.

I can see multiple way to limit the side effect of this behavior :

  • Remove the React.StrictMode (not ideal if you want React to check your code)
  • You could write tests for your components to make sure they behave correctly and then turn them off (or not) with global variables on a development environment to avoid the duplicated subscription behavior.
  • If you store data in a cache (e.g. Redux store) or display a table based on the event you receive from a subscription, use a dictionary instead of an array to easily override the duplicated data.

I agree, this is annoying. In my case I listen for events and display an alert for each of them in the UI which turns out to be duplicated. However the problem does not come from Apollo-Client, it is React strict mode behavior.. I don't think we can expect a fix coming from Apollo to remove this behavior since it is external to Apollo-Client.

There is a reason for strict mode rendering twice. It is to prevent bugs in the coming concurrent mode. So this should be fixed in apollo-client.

The worst thing is that those duplicated observables leak and never get destroyed, so when you do client.resetStore() all of them get refetched and this most probably will throw errors because the auth token is not set anymore.
I'm surprised that this issue is completely ignored for almost half a year already.

Screenshot 2020-09-07 at 15 14 50
I'm still getting this issue, returning twice when is triggered the subscription

experiencing this now, seeing duplicate subscriptions. First thing I thought of was to use useEffect, then I found this thread.

im having this issue also

I have the same issue

The same issue

Same issue

same issue here, please help

https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts

can't wrap my head around why this is happening
but in DEV mode

this.applyMiddlewares (which returns a promise)

seems to be resolving twice for the same subscription
BUT... with a different id

private executeOperation(options: OperationOptions, handler: (error: Error[], result?: any) => void): string {
    if (this.client === null) {
      this.connect();
    }

    const opId = this.generateOperationId();
    this.operations[opId] = { options: options, handler };

    this.applyMiddlewares(options)
      .then(processedOptions => {
        this.checkOperationOptions(processedOptions, handler);
        if (this.operations[opId]) {
          this.operations[opId] = { options: processedOptions, handler };
          this.sendMessage(opId, MessageTypes.GQL_START, processedOptions);
        }
      })
      .catch(error => {
        this.unsubscribe(opId);
        handler(this.formatErrors(error));
      });

    return opId;
}

the odd thing is, even though this condition exists

if (this.operations[opId]) {
      this.operations[opId] = { options: processedOptions, handler };
      this.sendMessage(opId, MessageTypes.GQL_START, processedOptions);
}

AND opId is created only once BEFORE calling

this.applyMiddlewares(...)

The second time the promise is resolved
...the same condition evaluates to true
for an id that is not created and shouldn't exist in this.operations

didn't dig too much through the code, so this... could be totally unrelated to why the subscription is fired twice
but maybe its a good starting point

👍 same issue

I also can confirm the issue. Does someone have an idea of why this is happening? I don't really understand the relationship between <React.StrictMode/> and the subscriptions...

I also can confirm the issue. Does someone have an idea of why this is happening? I don't really understand the relationship between <React.StrictMode/> and the subscriptions...

Because in development StrictMode causes function component bodies to double-execute: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

The useSubscription hook is breaking contract with react by starting the subscription in function body.

I also can confirm the issue. Does someone have an idea of why this is happening? I don't really understand the relationship between <React.StrictMode/> and the subscriptions...

Because in development StrictMode causes function component bodies to double-execute: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

The useSubscription hook is breaking contract with react by starting the subscription in function body.

Thank you very much for the precision. I'll try to look on the useSubscription hook and maybe submit a PR to fix this.

I get multiple as well

Saw the same thing happening earlier removing StrictMode resolved it.

Was this page helpful?
0 / 5 - 0 ratings