Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apollo Client 3 | React.StrictMode | @client @export field causes re-renders when same @export field is also queried by another component #6634

Open
sheaosaurus opened this issue Jul 17, 2020 · 2 comments
Milestone

Comments

@sheaosaurus
Copy link

My team and I are trying to migrate our filter state from Redux to Apollo Local State. This filter state needs to be added as a variable to the server queries, and we were hoping to use @export to achieve this.

In our app, on mount, the following queries need to be resolved:

  1. Two or more separate filter components query their state from the cache using queries:
const GET_POKEMON_NAME_FILTER = gql`
  query {
    pokemonName @client
  }
`;`

`const GET_POKEMON_TYPE_FILTER = gql`
  query {
    pokemonType @client
  }
`;
  1. Any number of data table or visual graphics to query the server with the @export decorator in the query such as:
const GET_POKEMON = gql`
  query($name: String, $pokemonType: String) {
    pokemonName @client @export(as: "name")
    pokemonType @client @export(as: "pokemonType")
    pokemon(name: $name, pokemonType: $pokemonType) {
      id
      number
      name
    }
  }
`;

Intended outcome:

Given my understanding of @export, on mount, I expect the following to happen:

  1. Either the filter component or the data component is rendered first
  2. The @client field pokemonName is loaded in the cache already via cache.writeQuery (in our production app, this may not be possible other server request needing to be made before the defaults can be set)
  3. The filter component reads from the cache using useQuery, grabs the pokemonName and renders twice to show loading as true then the return data
  4. The data component runs the @export, resolves the pokemonName field, and runs the server request
  5. The data is returned to the component once after the server request is complete with minimal rerenders

Actual outcome:

  1. The filter component renders 6 times (via a console.count) with loading equal to true four times, and false twice. The data for this component is defined with the correct default value all six times.

  2. The data component renders 8 times. The last two times it renders, the data object contains the correctly returned data from the server.

  3. The logic in the return and children component is run again with the same data in both components, unless React.memo is used to block the rerender. (If you place a React.memo'd component in the render function of the data component, it only renders once. This shows that the returned objects are indeed equal).


How to reproduce the issue:
Please see codesandbox link below with a very basic implementation of our use-case that also has the same re-rendering issue detailed above.

CodeSandbox: https://codesandbox.io/s/apollo-local-state-export-fields-3cd46?file=/src/App.js

In the sandbox, I query a pokemon API in a data component using a pokemon name variable. A sibling filter component also needs to query this variable from local state as well.

  • There are commented out console log/count statements in the data and filter components to show the amount of rerenders and what data is present on each render.
  • I also tested this using a local only field and the same outcome as below was observed.
  • We are almost certain reactive variables are not a use-case here

It appears as if the hooks are updating their internal state whenever the components rerender or the hook receives an update to one of its state values (ie loading, data etc).

The downstream consequence of this seems to be that when the data component changes, it toggles its loading state, causing the filter component to rerender as well even though the filter data has not changed.

Given this multiple rendering, I wanted to ensure that I was not trying to extend @export beyond its capabilities or if the intended outcome is correct and this is a bug.

Thank you for any assistance.


Versions

System: OS: macOS 10.15.4 Binaries: Node: 12.16.1 - /usr/local/bin/node Yarn: 1.22.4 - ~/.yarn/bin/yarn npm: 6.13.4 - /usr/local/bin/npm Browsers: Chrome: 84.0.4147.89 Safari: 13.1 npmPackages: @apollo/client: ^3.0.1 => 3.0.1

@sheaosaurus
Copy link
Author

sheaosaurus commented Jul 23, 2020

To update this ticket, my team and I did a debugging session today and found that the cause for the re-rendering was React.StrictMode.

A brief background for anyone reading this who is not aware, React.StrictMode, which runs in development mode only, intentionally double renders the application to check for legacy code, unwanted side-effects, etc.
React.StrictMode Docs

StrictMode Enabled:
Our Data component renders 6-8 times with the correct data, as described in the actual outcome in the OP.

StrictMode Not-Enabled:
In development mode, the Data component renders 2 times with no data, while the filter component renders twice and correctly receives its filter.

The high level implications of this are that any code we tested and expect to work in dev will break in production.


Updated Intended Outcome

With React.StrictMode not enabled, I expect the data returned from the server to be on the data object in the component.
From the picture below, the data is returned from the server and placed on the Root.Query object.

Screen Shot 2020-07-23 at 5 39 56 PM


How to Reproduce

Please see updated Codesandbox with React.StrictMode removed:
https://codesandbox.io/s/apollo-local-state-export-fields-strict-mode-disabled-rqmn1?file=/src/App.js

@sheaosaurus sheaosaurus changed the title Apollo Client 3: @client @export field causes re-renders when same @export field is also queried by another component Apollo Client 3 | React.StrictMode | @client @export field causes re-renders when same @export field is also queried by another component Jul 24, 2020
@jcreighton jcreighton self-assigned this Jan 14, 2021
@benknight
Copy link

I just discovered this quirk today as well. Basically the double-rendering causes Apollo to create duplicate watched queries, so over time while using the application, the number of watched queries grows unbounded, and if you're using something like polling for example, because of the duplicate queries the server will be polled increasingly more frequently. I thought this was a bug in my code but realized it's just affecting development mode. Anyway long story short I've stopped using React.StrictMode so that Apollo queries behave similarly in dev and prod environments.

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

No branches or pull requests

6 participants