Skip to content

useQuery onCompleted callback is one render out of date #12316

Open
@DavidA94

Description

@DavidA94

Issue Description

I am querying a GraphQL endpoint with dynamic variables. I expect to transform the data inside the onCompleted function, and set a variable based on the dynamic variable. However, the Apollo client is caching the old onCompleted callback, so that it's one out of date, which gives me incorrect data.

To show this, I've written an MRE which console logs inside the onCompleted function. From the parent, I pass a prop down which is printed in the log statement. The prop is a number and each re-render increases it by one. Instead of seeing n in my logs, though, I see n - 1 for everything after the first one.

Render Count Prop Value Expected Log Value Actual Log Value
Render #1 0 Completed callback: 0 Completed callback: 0
Render #2 1 Completed callback: 1 Completed callback: 0
Render #3 2 Completed callback: 2 Completed callback: 1
Render #4 3 Completed callback: 3 Completed callback: 2
Render #5 4 Completed callback: 4 Completed callback: 3

MRE code

index.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://flyby-router-demo.herokuapp.com/',
  cache: new InMemoryCache(),
});

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>
)

App.tsx

import "./App.css";
import { useQuery, gql } from "@apollo/client";
import { useState } from "react";

const GET_LOCATION = gql`
  query GetLocationDetails($locationId: ID!) {
    location(id: $locationId) {
      id
      name
      description
      photo
      overallRating
      reviewsForLocation {
        id
        comment
        rating
      }
    }
  }
`;

function DisplayLocations({ lastStateCount }) {
  const { loading, error, data } = useQuery(GET_LOCATION, {
    variables: {
      locationId: "loc-1",
      ignore: lastStateCount,
    },
    onCompleted: (data) => {
      console.log("Completed callback:", lastStateCount);
    },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error : {error.message}</p>;

  return [data.location].map(({ id, name, description, photo }) => (
    <div key={id}>
      <h3>
        {name} ({id})
      </h3>
      <img width="400" height="250" alt="location-reference" src={`${photo}`} />
      <br />
      <b>About this location:</b>
      <p>{description}</p>
      <br />
    </div>
  ));
}

export default function App() {
  const [state, setState] = useState(0);

  return (
    <main>
      <button onClick={() => setState(state + 1)}>
        Click me to increase the counters.
      </button>
      <p>
        <b>Note:</b> The below text is wrong for the first render. Click the
        button for the text to match the console logs. The first one works, but
        it fails from there on out.
      </p>
      Expected console log: "Complete callback: <b>{state}</b>"
      <br />
      What you see: "Complete callback: <b>{state - 1}</b>"
      <br />
      <br />
      <DisplayLocations lastStateCount={state} />
    </main>
  );
}

package.json

  "dependencies": {
    "@apollo/client": "^3.12.8"
  },

If it'll load, I've also made a Repl here: https://replit.com/@DavidAntonucci/ApolloBugMRE#src/App.tsx

Link to Reproduction

https://replit.com/@DavidAntonucci/ApolloBugMRE#src/App.tsx

Reproduction Steps

No response

@apollo/client version

3.12.8

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions