Repository: FormidableLabs/urql
Pull request #894 (2020-07-05 18:52:16)

(next-urql) - Pass resetUrqlClient method to App/Page
<!--
  Thanks for opening a pull request! We appreciate your dedication and help!
  Before submitting your pull request, please make sure to read our CONTRIBUTING guide.

  The best contribution is always a PR! However, if you're starting to work on a large
  change, it's best to make sure to open an issue first.

  If this PR is already related to an issue, please reference it like so:
  Resolves #123
-->

## Summary

Resolves: https://github.com/FormidableLabs/urql/issues/832

We can rename this to `_dangerous_reset_store` since this resets all the exchange states, ....

## Set of changes

- Add function to invalidate current client instance

changeset-bot[bot]: ###  💥  No Changeset

Latest commit: 73ba364ad6e66175a285cd1b99b2ac989902a4f4

Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂

**If these changes should be published to npm, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/FormidableLabs/urql/new/next-reset-client?filename=.changeset/six-lions-agree.md&value=---%0A%22next-urql%22%3A%20patch%0A---%0A%0A(next-urql)%20-%20make%20initial%20draft%20for%20resetting%20the%20active%20urql-client%20instance%0A)


zvictor: I am getting `Error: Invalid hook call` once I switch to this branch.

![image](https://user-images.githubusercontent.com/181076/88284800-4e876400-ccee-11ea-882a-511b325e8e4f.png)

It looks like React doesn't recognise the code within `withUrqlClient` as a react context. 

Patch:
diff --git a/.changeset/hot-turkeys-deliver.md b/.changeset/hot-turkeys-deliver.md
new file mode 100644
index 00000000..4c6eb0db
--- /dev/null
+++ b/.changeset/hot-turkeys-deliver.md
@@ -0,0 +1,7 @@
+---
+'@urql/preact': minor
+'urql': minor
+'@urql/svelte': minor
+---
+
+Add the operation to the query, mutation and subscription result.
diff --git a/.changeset/rotten-pandas-obey.md b/.changeset/rotten-pandas-obey.md
new file mode 100644
index 00000000..0bd45419
--- /dev/null
+++ b/.changeset/rotten-pandas-obey.md
@@ -0,0 +1,5 @@
+---
+'next-urql': minor
+---
+
+Add the option to reset the client on a next-urql application.
diff --git a/docs/advanced/server-side-rendering.md b/docs/advanced/server-side-rendering.md
index f4cb68a3..5dfe00b6 100644
--- a/docs/advanced/server-side-rendering.md
+++ b/docs/advanced/server-side-rendering.md
@@ -240,3 +240,11 @@ export default withUrqlClient(ssrExchange => ({
 
 Unless the component that is being wrapped already has a `getInitialProps` method, `next-urql` won't add its own SSR logic, which automatically fetches queries during
 server-side rendering. This can be explicitly enabled by passing the `{ ssr: true }` option as a second argument to `withUrqlClient`.
+
+### Resetting the client instance
+
+In rare scenario's you possibly will have to reset the client instance (reset all cache, ...), this is an uncommon scenario
+and we consider it "unsafe" so evaluate this carefully for yourself.
+
+When this does seem like the appropriate solution any component wrapped with `withUrqlClient` will receive the `resetUrqlClient`
+property, when invoked this will create a new top-level client and reset all prior operations.
diff --git a/docs/graphcache/custom-updates.md b/docs/graphcache/custom-updates.md
index 83d93ab7..4bd2516c 100644
--- a/docs/graphcache/custom-updates.md
+++ b/docs/graphcache/custom-updates.md
@@ -216,6 +216,27 @@ const cache = cacheExchange({
 });
 ```
 
+Let's combine the above example with invalidating fields, imagine the scenario where we add a todo but
+rather than manually pushing it on all the lists we just want to refetch the lists.
+
+```js
+const cache = cacheExchange({
+  updates: {
+    Mutation: {
+      addTodo: (result, args, cache, info) => {
+        const todoQueries = cache.inspectFields('Query').filter(x => x.fieldName === 'todos');
+
+        todosQueries.forEach(({ fieldName, arguments: variables }) => {
+          cache.invalidate('Query', fieldName, variables);
+        });
+      },
+    },
+  },
+});
+```
+
+Now when we come onto a list we'll know that this list needs to be refetched.
+
 ## Optimistic updates
 
 If we know what result a mutation may return, why wait for the GraphQL API to fulfill our mutations?
diff --git a/exchanges/graphcache/default-storage/package.json b/exchanges/graphcache/default-storage/package.json
index 959545a0..a861803f 100644
--- a/exchanges/graphcache/default-storage/package.json
+++ b/exchanges/graphcache/default-storage/package.json
@@ -15,7 +15,7 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
-    "@urql/core": ">=1.12.0",
+    "@urql/core": ">=1.12.1",
     "wonka": "^4.0.14"
   }
 }
diff --git a/packages/next-urql/README.md b/packages/next-urql/README.md
index 4052b785..c75ba67a 100644
--- a/packages/next-urql/README.md
+++ b/packages/next-urql/README.md
@@ -121,6 +121,14 @@ withUrqlClient((_ssrExchange, ctx) => ({
 
 In client-side SPAs using `urql`, you typically configure the Client yourself and pass it as the `value` prop to `urql`'s context `Provider`. `withUrqlClient` handles setting all of this up for you under the hood. By default, you'll be opted into server-side `Suspense` and have the necessary `exchanges` set up for you, including the [`ssrExchange`](https://formidable.com/open-source/urql/docs/api/#ssrexchange-exchange-factory).
 
+### Resetting the client instance
+
+In rare scenario's you possibly will have to reset the client instance (reset all cache, ...), this is an uncommon scenario
+and we consider it "unsafe" so evaluate this carefully for yourself.
+
+When this does seem like the appropriate solution any component wrapped with `withUrqlClient` will receive the `resetUrqlClient`
+property, when invoked this will create a new top-level client and reset all prior operations.
+
 #### `exchanges`
 
 When you're using `withUrqlClient` and you don't return an `exchanges` property we'll assume you wanted the default exchanges, these contain: `dedupExchange`, `cacheExchange`, `ssrExchange` (the one you received as a first argument) and the `fetchExchange`.
diff --git a/packages/next-urql/src/init-urql-client.ts b/packages/next-urql/src/init-urql-client.ts
index ade570e7..c37fdaac 100644
--- a/packages/next-urql/src/init-urql-client.ts
+++ b/packages/next-urql/src/init-urql-client.ts
@@ -3,6 +3,10 @@ import 'isomorphic-unfetch';
 
 let urqlClient: Client | null = null;
 
+export function resetClient() {
+  urqlClient = null;
+}
+
 export function initUrqlClient(
   clientOptions: ClientOptions,
   canEnableSuspense: boolean
diff --git a/packages/next-urql/src/with-urql-client.tsx b/packages/next-urql/src/with-urql-client.tsx
index e6feae25..b00a2b27 100644
--- a/packages/next-urql/src/with-urql-client.tsx
+++ b/packages/next-urql/src/with-urql-client.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { NextPage, NextPageContext } from 'next';
 import NextApp, { AppContext } from 'next/app';
 import ssrPrepass from 'react-ssr-prepass';
@@ -10,7 +10,7 @@ import {
   fetchExchange,
 } from 'urql';
 
-import { initUrqlClient } from './init-urql-client';
+import { initUrqlClient, resetClient } from './init-urql-client';
 import {
   NextUrqlClientConfig,
   NextUrqlContext,
@@ -35,6 +35,9 @@ export function withUrqlClient(
     );
 
     const withUrql = ({ urqlClient, urqlState, ...rest }: WithUrqlProps) => {
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const forceUpdate = useState(0);
+
       // eslint-disable-next-line react-hooks/rules-of-hooks
       const client = React.useMemo(() => {
         if (urqlClient) {
@@ -57,11 +60,21 @@ export function withUrqlClient(
 
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         return initUrqlClient(clientConfig, shouldBindGetInitialprops)!;
-      }, [urqlClient, urqlState]);
+      }, [urqlClient, urqlState, forceUpdate[0]]);
+
+      const resetUrqlClient = () => {
+        resetClient();
+        ssr = ssrExchange({ initialState: undefined });
+        forceUpdate[1](forceUpdate[0] + 1);
+      };
 
       return (
         <Provider value={client}>
-          <AppOrPage urqlClient={client} {...rest} />
+          <AppOrPage
+            urqlClient={client}
+            resetUrqlClient={resetUrqlClient}
+            {...rest}
+          />
         </Provider>
       );
     };
diff --git a/packages/preact-urql/src/hooks/useMutation.ts b/packages/preact-urql/src/hooks/useMutation.ts
index fc7d5eb1..84159640 100644
--- a/packages/preact-urql/src/hooks/useMutation.ts
+++ b/packages/preact-urql/src/hooks/useMutation.ts
@@ -2,10 +2,11 @@ import { useCallback } from 'preact/hooks';
 import { DocumentNode } from 'graphql';
 import { pipe, toPromise } from 'wonka';
 import {
-  OperationResult,
+  Operation,
   OperationContext,
   CombinedError,
   createRequest,
+  OperationResult,
 } from '@urql/core';
 import { useClient } from '../context';
 import { useImmediateState } from './useImmediateState';
@@ -17,6 +18,7 @@ export interface UseMutationState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseMutationResponse<T, V> = [
@@ -56,6 +58,7 @@ export const useMutation = <T = any, V = object>(
           data: result.data,
           error: result.error,
           extensions: result.extensions,
+          operation: result.operation,
         });
         return result;
       });
diff --git a/packages/preact-urql/src/hooks/useQuery.ts b/packages/preact-urql/src/hooks/useQuery.ts
index 7d7d8fe1..4d1c1a34 100644
--- a/packages/preact-urql/src/hooks/useQuery.ts
+++ b/packages/preact-urql/src/hooks/useQuery.ts
@@ -1,7 +1,12 @@
 import { DocumentNode } from 'graphql';
 import { pipe, subscribe, onEnd } from 'wonka';
 import { useRef, useCallback } from 'preact/hooks';
-import { OperationContext, RequestPolicy, CombinedError } from '@urql/core';
+import {
+  OperationContext,
+  RequestPolicy,
+  CombinedError,
+  Operation,
+} from '@urql/core';
 
 import { useClient } from '../context';
 import { useRequest } from './useRequest';
@@ -13,6 +18,7 @@ export const initialState: UseQueryState<any> = {
   stale: false,
   data: undefined,
   error: undefined,
+  operation: undefined,
   extensions: undefined,
 };
 
@@ -31,6 +37,7 @@ export interface UseQueryState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseQueryResponse<T> = [
@@ -75,6 +82,7 @@ export const useQuery = <T = any, V = object>(
             error: result.error,
             extensions: result.extensions,
             stale: !!result.stale,
+            operation: result.operation,
           });
         })
       );
diff --git a/packages/preact-urql/src/hooks/useSubscription.ts b/packages/preact-urql/src/hooks/useSubscription.ts
index 34c0203c..ac24fc61 100644
--- a/packages/preact-urql/src/hooks/useSubscription.ts
+++ b/packages/preact-urql/src/hooks/useSubscription.ts
@@ -1,7 +1,7 @@
 import { DocumentNode } from 'graphql';
 import { useCallback, useRef } from 'preact/hooks';
 import { pipe, onEnd, subscribe } from 'wonka';
-import { CombinedError, OperationContext } from '@urql/core';
+import { CombinedError, OperationContext, Operation } from '@urql/core';
 import { useClient } from '../context';
 import { useRequest } from './useRequest';
 import { noop, initialState } from './useQuery';
@@ -23,6 +23,7 @@ export interface UseSubscriptionState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseSubscriptionResponse<T> = [
@@ -72,6 +73,7 @@ export const useSubscription = <T = any, R = T, V = object>(
             error: result.error,
             extensions: result.extensions,
             stale: !!result.stale,
+            operation: result.operation,
           }));
         })
       );
diff --git a/packages/react-urql/src/hooks/constants.ts b/packages/react-urql/src/hooks/constants.ts
index 258490cb..1852afcd 100644
--- a/packages/react-urql/src/hooks/constants.ts
+++ b/packages/react-urql/src/hooks/constants.ts
@@ -4,4 +4,5 @@ export const initialState = {
   error: undefined,
   data: undefined,
   extensions: undefined,
+  operation: undefined,
 };
diff --git a/packages/react-urql/src/hooks/useMutation.ts b/packages/react-urql/src/hooks/useMutation.ts
index bfd543b2..77f16b09 100644
--- a/packages/react-urql/src/hooks/useMutation.ts
+++ b/packages/react-urql/src/hooks/useMutation.ts
@@ -7,6 +7,7 @@ import {
   OperationContext,
   CombinedError,
   createRequest,
+  Operation,
 } from '@urql/core';
 
 import { useClient } from '../context';
@@ -18,6 +19,7 @@ export interface UseMutationState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseMutationResponse<T, V> = [
@@ -54,6 +56,7 @@ export function useMutation<T = any, V = object>(
             data: result.data,
             error: result.error,
             extensions: result.extensions,
+            operation: result.operation,
           });
         }
         return result;
diff --git a/packages/react-urql/src/hooks/useQuery.ts b/packages/react-urql/src/hooks/useQuery.ts
index e2410ce4..2aa4f404 100644
--- a/packages/react-urql/src/hooks/useQuery.ts
+++ b/packages/react-urql/src/hooks/useQuery.ts
@@ -1,7 +1,12 @@
 import { DocumentNode } from 'graphql';
 import { useCallback, useMemo } from 'react';
 import { pipe, concat, fromValue, switchMap, map, scan } from 'wonka';
-import { CombinedError, OperationContext, RequestPolicy } from '@urql/core';
+import {
+  CombinedError,
+  OperationContext,
+  RequestPolicy,
+  Operation,
+} from '@urql/core';
 
 import { useClient } from '../context';
 import { useSource, useBehaviourSubject } from './useSource';
@@ -23,6 +28,7 @@ export interface UseQueryState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseQueryResponse<T> = [
@@ -68,11 +74,12 @@ export function useQuery<T = any, V = object>(
             fromValue({ fetching: true, stale: false }),
             pipe(
               query$,
-              map(({ stale, data, error, extensions }) => ({
+              map(({ stale, data, error, extensions, operation }) => ({
                 fetching: false,
                 stale: !!stale,
                 data,
                 error,
+                operation,
                 extensions,
               }))
             ),
diff --git a/packages/react-urql/src/hooks/useSubscription.ts b/packages/react-urql/src/hooks/useSubscription.ts
index be42d67f..20726b4b 100644
--- a/packages/react-urql/src/hooks/useSubscription.ts
+++ b/packages/react-urql/src/hooks/useSubscription.ts
@@ -1,7 +1,7 @@
 import { DocumentNode } from 'graphql';
 import { useCallback, useRef, useMemo } from 'react';
 import { pipe, concat, fromValue, switchMap, map, scan } from 'wonka';
-import { CombinedError, OperationContext } from '@urql/core';
+import { CombinedError, OperationContext, Operation } from '@urql/core';
 
 import { useClient } from '../context';
 import { useSource, useBehaviourSubject } from './useSource';
@@ -23,6 +23,7 @@ export interface UseSubscriptionState<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export type UseSubscriptionResponse<T> = [
@@ -72,12 +73,13 @@ export function useSubscription<T = any, R = T, V = object>(
             fromValue({ fetching: true, stale: false }),
             pipe(
               subscription$,
-              map(({ stale, data, error, extensions }) => ({
+              map(({ stale, data, error, extensions, operation }) => ({
                 fetching: true,
                 stale: !!stale,
                 data,
                 error,
                 extensions,
+                operation,
               }))
             ),
             // When the source proactively closes, fetching is set to false
diff --git a/packages/svelte-urql/src/operations/constants.ts b/packages/svelte-urql/src/operations/constants.ts
index 258490cb..2a3c57f4 100644
--- a/packages/svelte-urql/src/operations/constants.ts
+++ b/packages/svelte-urql/src/operations/constants.ts
@@ -3,5 +3,6 @@ export const initialState = {
   stale: false,
   error: undefined,
   data: undefined,
+  operation: undefined,
   extensions: undefined,
 };
diff --git a/packages/svelte-urql/src/operations/query.ts b/packages/svelte-urql/src/operations/query.ts
index 24e8fbed..47a01dda 100644
--- a/packages/svelte-urql/src/operations/query.ts
+++ b/packages/svelte-urql/src/operations/query.ts
@@ -14,7 +14,12 @@ import {
   toPromise,
 } from 'wonka';
 
-import { RequestPolicy, OperationContext, CombinedError } from '@urql/core';
+import {
+  RequestPolicy,
+  OperationContext,
+  CombinedError,
+  Operation,
+} from '@urql/core';
 
 import { Readable } from 'svelte/store';
 import { DocumentNode } from 'graphql';
@@ -37,6 +42,7 @@ export interface QueryResult<T> {
   data?: T;
   error?: CombinedError;
   extensions?: Record<string, any>;
+  operation?: Operation;
 }
 
 export interface QueryStore<T = any, V = object>
@@ -67,12 +73,13 @@ export const query = <T = any, V = object>(
             pollInterval: args.pollInterval,
             ...args.context,
           }),
-          map(({ stale, data, error, extensions }) => ({
+          map(({ stale, data, error, extensions, operation }) => ({
             fetching: false,
             stale: !!stale,
             data,
             error,
             extensions,
+            operation,
           }))
         ),
         // When the source proactively closes, fetching is set to false
diff --git a/packages/svelte-urql/src/operations/subscription.ts b/packages/svelte-urql/src/operations/subscription.ts
index 29746997..972e2ea8 100644
--- a/packages/svelte-urql/src/operations/subscription.ts
+++ b/packages/svelte-urql/src/operations/subscription.ts
@@ -12,7 +12,7 @@ import {
   publish,
 } from 'wonka';
 
-import { OperationContext, CombinedError } from '@urql/core';
+import { OperationContext, CombinedError, Operation } from '@urql/core';
 import { Readable } from 'svelte/store';
 import { DocumentNode } from 'graphql';
 
@@ -24,6 +24,7 @@ export interface SubscriptionArguments<V> {
   variables?: V;
   pause?: boolean;
   context?: Partial<OperationContext>;
+  operation?: Operation;
 }
 
 export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R;
@@ -62,12 +63,13 @@ export const subscription = <T = any, R = T, V = object>(
         fromValue({ fetching: true, stale: false }),
         pipe(
           client.subscription<T>(args.query, args.variables, args.context),
-          map(({ stale, data, error, extensions }) => ({
+          map(({ stale, data, error, extensions, operation }) => ({
             fetching: false,
             stale: !!stale,
             data,
             error,
             extensions,
+            operation,
           }))
         ),
         // When the source proactively closes, fetching is set to false


Test patch:
diff --git a/packages/react-urql/src/hooks/__snapshots__/useMutation.test.tsx.snap b/packages/react-urql/src/hooks/__snapshots__/useMutation.test.tsx.snap
index e51218fe..2da6d2de 100644
--- a/packages/react-urql/src/hooks/__snapshots__/useMutation.test.tsx.snap
+++ b/packages/react-urql/src/hooks/__snapshots__/useMutation.test.tsx.snap
@@ -6,6 +6,7 @@ Object {
   "error": undefined,
   "extensions": undefined,
   "fetching": false,
+  "operation": undefined,
   "stale": false,
 }
 `;
diff --git a/packages/react-urql/src/hooks/__snapshots__/useQuery.test.tsx.snap b/packages/react-urql/src/hooks/__snapshots__/useQuery.test.tsx.snap
index 8e42fcf5..353e67e3 100644
--- a/packages/react-urql/src/hooks/__snapshots__/useQuery.test.tsx.snap
+++ b/packages/react-urql/src/hooks/__snapshots__/useQuery.test.tsx.snap
@@ -6,6 +6,7 @@ Object {
   "error": undefined,
   "extensions": undefined,
   "fetching": true,
+  "operation": undefined,
   "stale": false,
 }
 `;
diff --git a/packages/react-urql/src/hooks/__snapshots__/useSubscription.test.tsx.snap b/packages/react-urql/src/hooks/__snapshots__/useSubscription.test.tsx.snap
index bfca60e1..ac601240 100644
--- a/packages/react-urql/src/hooks/__snapshots__/useSubscription.test.tsx.snap
+++ b/packages/react-urql/src/hooks/__snapshots__/useSubscription.test.tsx.snap
@@ -6,6 +6,7 @@ Object {
   "error": 5678,
   "extensions": undefined,
   "fetching": true,
+  "operation": undefined,
   "stale": false,
 }
 `;