Repository: apollographql/apollo-client

Problem statement:
[4.0] Remove deprecated `partialRefetch` option
https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-partialrefetch


Hints:

Patch:
diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts
--- a/src/core/ObservableQuery.ts
+++ b/src/core/ObservableQuery.ts
@@ -308,7 +308,6 @@ export class ObservableQuery<
       if (
         __DEV__ &&
         !diff.complete &&
-        !this.options.partialRefetch &&
         !result.loading &&
         !result.data &&
         !result.error
diff --git a/src/core/watchQueryOptions.ts b/src/core/watchQueryOptions.ts
--- a/src/core/watchQueryOptions.ts
+++ b/src/core/watchQueryOptions.ts
@@ -78,9 +78,6 @@ export interface QueryOptions<TVariables = OperationVariables, TData = any> {
   /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#returnPartialData:member} */
   returnPartialData?: boolean;
 
-  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#partialRefetch:member} */
-  partialRefetch?: boolean;
-
   /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#canonizeResults:member} */
   canonizeResults?: boolean;
 }
@@ -136,9 +133,6 @@ export interface SharedWatchQueryOptions<
   /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#returnPartialData:member} */
   returnPartialData?: boolean;
 
-  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#partialRefetch:member} */
-  partialRefetch?: boolean;
-
   /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#canonizeResults:member} */
   canonizeResults?: boolean;
 
diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts
--- a/src/react/hooks/useQuery.ts
+++ b/src/react/hooks/useQuery.ts
@@ -11,7 +11,6 @@
  * makeWatchQueryOptions
  * isSSRAllowed
  * disableNetworkFetches
- * partialRefetch
  * renderPromises
  * isSyncSSR
  * callbacks
@@ -260,7 +259,6 @@ export function useQueryInternals<
   const isSyncSSR = !!renderPromises;
   const disableNetworkFetches = client.disableNetworkFetches;
   const ssrAllowed = options.ssr !== false && !options.skip;
-  const partialRefetch = options.partialRefetch;
 
   const makeWatchQueryOptions = createMakeWatchQueryOptions(
     client,
@@ -301,7 +299,6 @@ export function useQueryInternals<
     options,
     watchQueryOptions,
     disableNetworkFetches,
-    partialRefetch,
     isSyncSSR,
     {
       onCompleted: options.onCompleted || noop,
@@ -329,7 +326,6 @@ function useObservableSubscriptionResult<
   options: QueryHookOptions<NoInfer<TData>, NoInfer<TVariables>>,
   watchQueryOptions: Readonly<WatchQueryOptions<TVariables, TData>>,
   disableNetworkFetches: boolean,
-  partialRefetch: boolean | undefined,
   isSyncSSR: boolean,
   callbacks: {
     onCompleted: (data: MaybeMasked<TData>) => void;
@@ -410,7 +406,6 @@ function useObservableSubscriptionResult<
             resultData,
             observable,
             client,
-            partialRefetch,
             handleStoreChange,
             callbackRef.current
           );
@@ -445,7 +440,6 @@ function useObservableSubscriptionResult<
               resultData,
               observable,
               client,
-              partialRefetch,
               handleStoreChange,
               callbackRef.current
             );
@@ -468,33 +462,14 @@ function useObservableSubscriptionResult<
         };
       },
 
-      [
-        disableNetworkFetches,
-        isSyncSSR,
-        observable,
-        resultData,
-        partialRefetch,
-        client,
-      ]
+      [disableNetworkFetches, isSyncSSR, observable, resultData, client]
     ),
     () =>
       currentResultOverride ||
-      getCurrentResult(
-        resultData,
-        observable,
-        callbackRef.current,
-        partialRefetch,
-        client
-      ),
+      getCurrentResult(resultData, observable, callbackRef.current, client),
     () =>
       currentResultOverride ||
-      getCurrentResult(
-        resultData,
-        observable,
-        callbackRef.current,
-        partialRefetch,
-        client
-      )
+      getCurrentResult(resultData, observable, callbackRef.current, client)
   );
 }
 
@@ -656,7 +631,6 @@ function setResult<TData, TVariables extends OperationVariables>(
   resultData: InternalResult<TData, TVariables>,
   observable: ObservableQuery<TData, TVariables>,
   client: ApolloClient<object>,
-  partialRefetch: boolean | undefined,
   forceUpdate: () => void,
   callbacks: Callbacks<TData>
 ) {
@@ -674,7 +648,7 @@ function setResult<TData, TVariables extends OperationVariables>(
   }
 
   resultData.current = toQueryResult(
-    unsafeHandlePartialRefetch(nextResult, observable, partialRefetch),
+    nextResult,
     resultData.previousData,
     observable,
     client
@@ -716,21 +690,17 @@ function getCurrentResult<TData, TVariables extends OperationVariables>(
   resultData: InternalResult<TData, TVariables>,
   observable: ObservableQuery<TData, TVariables>,
   callbacks: Callbacks<TData>,
-  partialRefetch: boolean | undefined,
   client: ApolloClient<object>
 ): InternalQueryResult<TData, TVariables> {
   // Using this.result as a cache ensures getCurrentResult continues returning
   // the same (===) result object, unless state.setResult has been called, or
   // we're doing server rendering and therefore override the result below.
   if (!resultData.current) {
-    // WARNING: SIDE-EFFECTS IN THE RENDER FUNCTION
-    // this could call unsafeHandlePartialRefetch
     setResult(
       observable.getCurrentResult(),
       resultData,
       observable,
       client,
-      partialRefetch,
       () => {},
       callbacks
     );
@@ -779,34 +749,6 @@ export function toQueryResult<TData, TVariables extends OperationVariables>(
   return queryResult;
 }
 
-function unsafeHandlePartialRefetch<
-  TData,
-  TVariables extends OperationVariables,
->(
-  result: ApolloQueryResult<MaybeMasked<TData>>,
-  observable: ObservableQuery<TData, TVariables>,
-  partialRefetch: boolean | undefined
-): ApolloQueryResult<MaybeMasked<TData>> {
-  // TODO: This code should be removed when the partialRefetch option is
-  // removed. I was unable to get this hook to behave reasonably in certain
-  // edge cases when this block was put in an effect.
-  if (
-    result.partial &&
-    partialRefetch &&
-    !result.loading &&
-    (!result.data || Object.keys(result.data).length === 0) &&
-    observable.options.fetchPolicy !== "cache-only"
-  ) {
-    observable.refetch();
-    return {
-      ...result,
-      loading: true,
-      networkStatus: NetworkStatus.refetch,
-    };
-  }
-  return result;
-}
-
 const ssrDisabledResult = maybeDeepFreeze({
   loading: true,
   data: void 0 as any,
diff --git a/src/react/types/types.documentation.ts b/src/react/types/types.documentation.ts
--- a/src/react/types/types.documentation.ts
+++ b/src/react/types/types.documentation.ts
@@ -106,16 +106,6 @@ export interface QueryOptionsDocumentation {
    */
   refetchWritePolicy_suspense: unknown;
 
-  /**
-   * If `true`, causes a query refetch if the query result is detected as partial.
-   *
-   * The default value is `false`.
-   *
-   * @deprecated
-   * Setting this option is unnecessary in Apollo Client 3, thanks to a more consistent application of fetch policies. It might be removed in a future release.
-   */
-  partialRefetch: unknown;
-
   /**
    * Whether to canonize cache results before returning them. Canonization
    * takes some extra time, but it speeds up future deep equality comparisons.


Test patch:
diff --git a/src/__tests__/ApolloClient.ts b/src/__tests__/ApolloClient.ts
--- a/src/__tests__/ApolloClient.ts
+++ b/src/__tests__/ApolloClient.ts
@@ -2724,7 +2724,6 @@ describe("ApolloClient", () => {
             pollInterval: 100,
             notifyOnNetworkStatusChange: true,
             returnPartialData: true,
-            partialRefetch: true,
           },
         },
       });
diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts
--- a/src/core/__tests__/QueryManager/index.ts
+++ b/src/core/__tests__/QueryManager/index.ts
@@ -635,7 +635,6 @@ describe("QueryManager", () => {
           watchQuery: {
             fetchPolicy: "cache-and-network",
             returnPartialData: false,
-            partialRefetch: true,
             notifyOnNetworkStatusChange: true,
           },
           query: {
diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx
--- a/src/react/hooks/__tests__/useQuery.test.tsx
+++ b/src/react/hooks/__tests__/useQuery.test.tsx
@@ -6747,215 +6747,6 @@ describe("useQuery Hook", () => {
     });
   });
 
-  describe("Partial refetch", () => {
-    it("should attempt a refetch when data is missing and partialRefetch is true", async () => {
-      using consoleSpy = spyOnConsole("error");
-      const query = gql`
-        {
-          hello
-        }
-      `;
-
-      const link = mockSingleLink(
-        {
-          request: { query },
-          result: { data: {} },
-          delay: 20,
-        },
-        {
-          request: { query },
-          result: { data: { hello: "world" } },
-          delay: 20,
-        }
-      );
-
-      const client = new ApolloClient({
-        link,
-        cache: new InMemoryCache(),
-      });
-
-      const { result } = renderHook(
-        () =>
-          useQuery(query, {
-            partialRefetch: true,
-            notifyOnNetworkStatusChange: true,
-          }),
-        {
-          wrapper: ({ children }) => (
-            <ApolloProvider client={client}>{children}</ApolloProvider>
-          ),
-        }
-      );
-
-      expect(result.current.loading).toBe(true);
-      expect(result.current.data).toBe(undefined);
-      expect(result.current.error).toBe(undefined);
-      expect(result.current.networkStatus).toBe(NetworkStatus.loading);
-
-      await waitFor(
-        () => {
-          expect(result.current.networkStatus).toBe(NetworkStatus.refetch);
-        },
-        { interval: 1 }
-      );
-      expect(result.current.loading).toBe(true);
-      expect(result.current.data).toBe(undefined);
-      expect(result.current.error).toBe(undefined);
-
-      expect(consoleSpy.error).toHaveBeenCalledTimes(1);
-      expect(consoleSpy.error.mock.calls[0][0]).toMatch("Missing field");
-
-      await waitFor(
-        () => {
-          expect(result.current.networkStatus).toBe(NetworkStatus.ready);
-        },
-        { interval: 1 }
-      );
-
-      expect(result.current.loading).toBe(false);
-      expect(result.current.data).toEqual({ hello: "world" });
-      expect(result.current.error).toBe(undefined);
-    });
-
-    it("should attempt a refetch when data is missing and partialRefetch is true 2", async () => {
-      const query = gql`
-        query people {
-          allPeople(first: 1) {
-            people {
-              name
-            }
-          }
-        }
-      `;
-
-      const data = {
-        allPeople: { people: [{ name: "Luke Skywalker" }] },
-      };
-
-      using consoleSpy = spyOnConsole("error");
-      const link = mockSingleLink(
-        { request: { query }, result: { data: {} }, delay: 20 },
-        { request: { query }, result: { data }, delay: 20 }
-      );
-
-      const client = new ApolloClient({
-        link,
-        cache: new InMemoryCache(),
-      });
-
-      const { result } = renderHook(
-        () =>
-          useQuery(query, {
-            partialRefetch: true,
-            notifyOnNetworkStatusChange: true,
-          }),
-        {
-          wrapper: ({ children }) => (
-            <ApolloProvider client={client}>{children}</ApolloProvider>
-          ),
-        }
-      );
-
-      expect(result.current.loading).toBe(true);
-      expect(result.current.data).toBe(undefined);
-      expect(result.current.error).toBe(undefined);
-      expect(result.current.networkStatus).toBe(NetworkStatus.loading);
-
-      await waitFor(
-        () => {
-          expect(result.current.networkStatus).toBe(NetworkStatus.refetch);
-        },
-        { interval: 1 }
-      );
-      expect(result.current.loading).toBe(true);
-      expect(result.current.data).toBe(undefined);
-      expect(result.current.error).toBe(undefined);
-
-      expect(consoleSpy.error).toHaveBeenCalledTimes(1);
-      expect(consoleSpy.error.mock.calls[0][0]).toMatch("Missing field");
-
-      await waitFor(
-        () => {
-          expect(result.current.networkStatus).toBe(NetworkStatus.ready);
-        },
-        { interval: 1 }
-      );
-      expect(result.current.loading).toBe(false);
-      expect(result.current.data).toEqual(data);
-      expect(result.current.error).toBe(undefined);
-    });
-
-    it("should attempt a refetch when data is missing, partialRefetch is true and addTypename is false for the cache", async () => {
-      using consoleSpy = spyOnConsole("error");
-      const query = gql`
-        {
-          hello
-        }
-      `;
-
-      const link = mockSingleLink(
-        {
-          request: { query },
-          result: { data: {} },
-          delay: 20,
-        },
-        {
-          request: { query },
-          result: { data: { hello: "world" } },
-          delay: 20,
-        }
-      );
-
-      const client = new ApolloClient({
-        link,
-        // THIS LINE IS THE ONLY DIFFERENCE FOR THIS TEST
-        cache: new InMemoryCache({ addTypename: false }),
-      });
-
-      const wrapper = ({ children }: any) => (
-        <ApolloProvider client={client}>{children}</ApolloProvider>
-      );
-
-      using _disabledAct = disableActEnvironment();
-      const { takeSnapshot } = await renderHookToSnapshotStream(
-        () =>
-          useQuery(query, {
-            partialRefetch: true,
-            notifyOnNetworkStatusChange: true,
-          }),
-        { wrapper }
-      );
-
-      {
-        const result = await takeSnapshot();
-        expect(result.loading).toBe(true);
-        expect(result.data).toBe(undefined);
-        expect(result.error).toBe(undefined);
-        expect(result.networkStatus).toBe(NetworkStatus.loading);
-      }
-
-      {
-        const result = await takeSnapshot();
-        expect(result.networkStatus).toBe(NetworkStatus.refetch);
-        expect(result.loading).toBe(true);
-        expect(result.error).toBe(undefined);
-        expect(result.data).toBe(undefined);
-      }
-
-      const calls = consoleSpy.error.mock.calls;
-      expect(calls.length).toBe(1);
-      expect(calls[0][0]).toMatch("Missing field");
-
-      {
-        const result = await takeSnapshot();
-        expect(result.networkStatus).toBe(NetworkStatus.ready);
-        expect(result.loading).toBe(false);
-        expect(result.data).toEqual({ hello: "world" });
-        expect(result.error).toBe(undefined);
-      }
-    });
-  });
-
   describe("Client Resolvers", () => {
     it("should receive up to date @client(always: true) fields on entity update", async () => {
       const query = gql`