You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Two or more separate filter components query their state from the cache using queries:
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:
Either the filter component or the data component is rendered first
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)
The filter component reads from the cache using useQuery, grabs the pokemonName and renders twice to show loading as true then the return data
The data component runs the @export, resolves the pokemonName field, and runs the server request
The data is returned to the component once after the server request is complete with minimal rerenders
Actual outcome:
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.
The data component renders 8 times. The last two times it renders, the data object contains the correctly returned data from the server.
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.
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.
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.
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
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.
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:
Intended outcome:
Given my understanding of @export, on mount, I expect the following to happen:
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)useQuery
, grabs the pokemonName and renders twice to show loading as true then the return dataActual outcome:
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.
The data component renders 8 times. The last two times it renders, the data object contains the correctly returned data from the server.
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.
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
The text was updated successfully, but these errors were encountered: