apollographql / react-apollo

:recycle: React integration for Apollo Client
https://www.apollographql.com/docs/react/
MIT License
6.85k stars 790 forks source link

useSubscription triggers unnecessary rerenders #3549

Open borremosch opened 4 years ago

borremosch commented 4 years ago

I am using useSubscription in one of my components. I do not use any of the return values, because I am using a complicated protocol in my subscription, and I do not want to act on every message I receive. Instead, I am using the onSubscriptionData callback function. I was surprised to see that even though I only update state on some of the received messages, my component is rerendering continuously. It seems that using useSubscription will trigger a re-render every time subscription data is received, even if it is discarded.

It would be very useful to have a version of useSubscription that does not trigger a re-render whenever data is received, and instead let the user decide when to make state changes in onSubscriptionData. For now, I have had to put my subscription in a sub-component, just to make the superfluous re-rendering computationally cheaper.

I am using @apollo/react-hooks version 3.1.2.

  System:
    OS: Linux 5.0 Ubuntu 19.04 (Disco Dingo)
  Binaries:
    Node: 10.16.0 - /usr/bin/node
    Yarn: 1.19.0 - /usr/bin/yarn
    npm: 6.10.0 - ~/cubonacci/services/ui/node_modules/.bin/npm
  Browsers:
    Chrome: 77.0.3865.90
    Firefox: 69.0.1
  npmPackages:
    @apollo/react-components: ^3.1.2 => 3.1.2 
    @apollo/react-hoc: ^3.1.2 => 3.1.2 
    @apollo/react-hooks: ^3.1.2 => 3.1.2 
    apollo-cache-inmemory: ^1.6.3 => 1.6.3 
    apollo-client: ^2.6.4 => 2.6.4 
    apollo-link-http: ^1.5.16 => 1.5.16 
    apollo-link-ws: ^1.0.19 => 1.0.19 
safead commented 4 years ago

Same issue here

filipmares commented 4 years ago

My team is also seeing this issue. Causing way too many re-renders. We're going to try the same approach as mentioned by @borremosch.

0xdevalias commented 4 years ago

EDIT: re-reading your original issue, I think my below is actually irrelevant, and the real reason is just that the setResult is always going to be called, which will cause the re-renders: https://github.com/apollographql/apollo-client/blob/master/src/react/hooks/useSubscription.ts#L19-L23


@borremosch @filipmares I'm not 100% if this will solve your issues, but if you are defining your onSubscriptionData handler inline, every time the hook is run/processed, you're actually passing an entirely new anonymous function to the hook.

The general way to resolve this is to use React's useCallback handler:


Digging deeper to confirm the theory, I found the related code at the following:

 public setOptions(
    newOptions: CommonOptions<TOptions>,
    storePrevious: boolean = false
  ) {
    if (storePrevious && !equal(this.options, newOptions)) {
      this.previousOptions = this.options;
    }
    this.options = newOptions;
  }

In particular, the !equal(this.options, newOptions), which uses:

/**
 * Performs a deep equality check on two JavaScript values, tolerating cycles.
 */
export function equal(a: any, b: any): boolean {
  // ..snip..
}
icodeforlove commented 4 years ago

I am seeing this happening when enabling reconnect.

A rerender is triggered every XX seconds (depends on timeout/keep-alive).

Any idea on how to prevent this rerender?

tronxdev commented 4 years ago

Today I faced the same issue with useSubscription. Whenever a subscription is fired, a React component is re-rendered. It isn't mentioned in useSubscription's API doc. What can I do to prevent the re-rendering?

@0xdevalias , could you elaborate your solution for me? It sounds good, but I'm not sure I follow. Thanks in advance!

tronxdev commented 4 years ago

My team is also seeing this issue. Causing way too many re-renders. We're going to try the same approach as mentioned by @borremosch.

Hi @filipmares , did you resolve this issue? If yes, could you tell me how to?