Repository: urql-graphql/urql (ts)

Issue:
Missing "hasNext" on UseQueryState
### Describe the bug

I'm missing the `hasNext` type on the UseQueryState even though it is part of the actual result object

~/urql/packages/next-urql/src/useQuery.ts

### Reproduction

https://github.com/urql-graphql/urql/tree/main/examples/with-next


### DEMO
<img width="469" alt="Screenshot 2024-10-28 at 17 53 35" src="https://github.com/user-attachments/assets/5de3d142-70cd-4443-b4a1-7b9b1b4e9fd3">
<img width="483" alt="Screenshot 2024-10-28 at 17 24 29" src="https://github.com/user-attachments/assets/da07b0ad-82d8-43f5-bf88-e837d57bc70c">
<img width="636" alt="Screenshot 2024-10-28 at 17 24 22" src="https://github.com/user-attachments/assets/056f5be6-bfdc-4df3-a0f7-6ac5190cd046">
<img width="735" alt="Screenshot 2024-10-28 at 17 23 41" src="https://github.com/user-attachments/assets/e875f832-e1d2-4d53-8fe7-9555a6f4b3eb">



### Urql version

latest

### Validations

- [X] I can confirm that this is a bug report, and not a feature request, RFC, question, or discussion, for which [GitHub Discussions](https://github.com/urql-graphql/urql/discussions) should be used
- [X] Read the [docs](https://formidable.com/open-source/urql/docs/).
- [X] Follow our [Code of Conduct](https://github.com/urql-graphql/urql/blob/main/CODE_OF_CONDUCT.md)

Pull request description:
Add `hasNext` to query-state
Resolves #3702 

## Summary

Add `hasNext` to query-state, still have to perform this for other bindings.


Patch:
diff --git a/.changeset/chatty-toes-melt.md b/.changeset/chatty-toes-melt.md
new file mode 100644
index 00000000..3298a536
--- /dev/null
+++ b/.changeset/chatty-toes-melt.md
@@ -0,0 +1,9 @@
+---
+'@urql/preact': patch
+'@urql/svelte': patch
+'urql': patch
+'@urql/solid': patch
+'@urql/vue': patch
+---
+
+Add type for `hasNext` to the query and mutation results
diff --git a/docs/advanced/persistence-and-uploads.md b/docs/advanced/persistence-and-uploads.md
index 45381bca..3f90a2a8 100644
--- a/docs/advanced/persistence-and-uploads.md
+++ b/docs/advanced/persistence-and-uploads.md
@@ -126,6 +126,9 @@ In a browser, the `File` object may often be retrieved via a
 [file input](https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications),
 for example.
 
+> **Note:** If you are using your own version of `File` and `Blob` ensure you are properly extending the
+> so it can be properly identified as a file.
+
 The `@urql/core@4` package supports File Uploads natively, so we won't have to do any installation
 or setup work. When `urql` sees a `File` or a `Blob` anywhere in your `variables`, it switches to
 a `multipart/form-data` request, converts the request to a `FormData` object, according to the
diff --git a/packages/preact-urql/src/hooks/constants.ts b/packages/preact-urql/src/hooks/constants.ts
index 1852afcd..96788f54 100644
--- a/packages/preact-urql/src/hooks/constants.ts
+++ b/packages/preact-urql/src/hooks/constants.ts
@@ -1,6 +1,7 @@
 export const initialState = {
   fetching: false,
   stale: false,
+  hasNext: false,
   error: undefined,
   data: undefined,
   extensions: undefined,
diff --git a/packages/preact-urql/src/hooks/useMutation.ts b/packages/preact-urql/src/hooks/useMutation.ts
index a66bb54a..e08ed625 100644
--- a/packages/preact-urql/src/hooks/useMutation.ts
+++ b/packages/preact-urql/src/hooks/useMutation.ts
@@ -45,6 +45,8 @@ export interface UseMutationState<
   data?: Data;
   /** The {@link OperationResult.error} for the executed mutation. */
   error?: CombinedError;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: boolean;
   /** The {@link OperationResult.extensions} for the executed mutation. */
   extensions?: Record<string, any>;
   /** The {@link Operation} that the current state is for.
@@ -164,6 +166,7 @@ export function useMutation<
               fetching: false,
               stale: result.stale,
               data: result.data,
+              hasNext: result.hasNext,
               error: result.error,
               extensions: result.extensions,
               operation: result.operation,
diff --git a/packages/preact-urql/src/hooks/useQuery.ts b/packages/preact-urql/src/hooks/useQuery.ts
index cf3c4f9e..9248788c 100644
--- a/packages/preact-urql/src/hooks/useQuery.ts
+++ b/packages/preact-urql/src/hooks/useQuery.ts
@@ -137,6 +137,8 @@ export interface UseQueryState<
    * last `Operation` that the current state was for.
    */
   operation?: Operation<Data, Variables>;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: boolean;
 }
 
 /** Triggers {@link useQuery} to execute a new GraphQL query operation.
@@ -303,16 +305,18 @@ export function useQuery<
       return pipe(
         query$$,
         switchMap(query$ => {
-          if (!query$) return fromValue({ fetching: false, stale: false });
+          if (!query$)
+            return fromValue({ fetching: false, stale: false, hasNext: false });
 
           return concat([
             // Initially set fetching to true
             fromValue({ fetching: true, stale: false }),
             pipe(
               query$,
-              map(({ stale, data, error, extensions, operation }) => ({
+              map(({ stale, data, error, extensions, operation, hasNext }) => ({
                 fetching: false,
                 stale: !!stale,
+                hasNext,
                 data,
                 error,
                 operation,
@@ -320,7 +324,7 @@ export function useQuery<
               }))
             ),
             // When the source proactively closes, fetching is set to false
-            fromValue({ fetching: false, stale: false }),
+            fromValue({ fetching: false, stale: false, hasNext: false }),
           ]);
         }),
         // The individual partial results are merged into each previous result
diff --git a/packages/react-urql/src/hooks/state.ts b/packages/react-urql/src/hooks/state.ts
index bf8d4f4a..4bdde5fe 100644
--- a/packages/react-urql/src/hooks/state.ts
+++ b/packages/react-urql/src/hooks/state.ts
@@ -3,6 +3,7 @@ import * as React from 'react';
 export const initialState = {
   fetching: false,
   stale: false,
+  hasNext: false,
   error: undefined,
   data: undefined,
   extensions: undefined,
@@ -38,6 +39,7 @@ const isShallowDifferent = <T extends Record<string, any>>(a: T, b: T) => {
 interface Stateish {
   data?: any;
   error?: any;
+  hasNext: boolean;
   fetching: boolean;
   stale: boolean;
 }
diff --git a/packages/react-urql/src/hooks/useMutation.ts b/packages/react-urql/src/hooks/useMutation.ts
index 9964ae0f..523852df 100644
--- a/packages/react-urql/src/hooks/useMutation.ts
+++ b/packages/react-urql/src/hooks/useMutation.ts
@@ -47,6 +47,8 @@ export interface UseMutationState<
   error?: CombinedError;
   /** The {@link OperationResult.extensions} for the executed mutation. */
   extensions?: Record<string, any>;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: boolean;
   /** The {@link Operation} that the current state is for.
    *
    * @remarks
@@ -167,6 +169,7 @@ export function useMutation<
               error: result.error,
               extensions: result.extensions,
               operation: result.operation,
+              hasNext: result.hasNext,
             });
           }
         }),
diff --git a/packages/react-urql/src/hooks/useQuery.spec.ts b/packages/react-urql/src/hooks/useQuery.spec.ts
index 57710ac8..3910038e 100644
--- a/packages/react-urql/src/hooks/useQuery.spec.ts
+++ b/packages/react-urql/src/hooks/useQuery.spec.ts
@@ -62,6 +62,7 @@ describe('useQuery', () => {
     expect(state).toEqual({
       fetching: true,
       stale: false,
+      hasNext: false,
       extensions: undefined,
       error: undefined,
       data: undefined,
@@ -126,6 +127,7 @@ describe('useQuery', () => {
       fetching: false,
       stale: false,
       extensions: undefined,
+      hasNext: false,
       error: 1,
       data: 0,
     });
diff --git a/packages/react-urql/src/hooks/useQuery.ts b/packages/react-urql/src/hooks/useQuery.ts
index 439bbab4..39ec4018 100644
--- a/packages/react-urql/src/hooks/useQuery.ts
+++ b/packages/react-urql/src/hooks/useQuery.ts
@@ -125,6 +125,8 @@ export interface UseQueryState<
   data?: Data;
   /** The {@link OperationResult.error} for the executed query. */
   error?: CombinedError;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: boolean;
   /** The {@link OperationResult.extensions} for the executed query. */
   extensions?: Record<string, any>;
   /** The {@link Operation} that the current state is for.
diff --git a/packages/solid-urql/src/createMutation.ts b/packages/solid-urql/src/createMutation.ts
index 0e3a86bc..97b6d3a7 100644
--- a/packages/solid-urql/src/createMutation.ts
+++ b/packages/solid-urql/src/createMutation.ts
@@ -44,6 +44,8 @@ export type CreateMutationState<
    * last `Operation` that the current state was for.
    */
   operation?: Operation<Data, Variables>;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: boolean;
 };
 
 /** Triggers {@link createMutation} to execute its GraphQL mutation operation.
@@ -140,6 +142,7 @@ export const createMutation = <
   const initialResult: CreateMutationState<Data, Variables> = {
     operation: undefined,
     fetching: false,
+    hasNext: false,
     stale: false,
     data: undefined,
     error: undefined,
@@ -167,6 +170,7 @@ export const createMutation = <
             error: result.error,
             extensions: result.extensions,
             operation: result.operation,
+            hasNext: result.hasNext,
           });
         });
       }),
diff --git a/packages/solid-urql/src/createQuery.ts b/packages/solid-urql/src/createQuery.ts
index 09fa22d2..4141f702 100644
--- a/packages/solid-urql/src/createQuery.ts
+++ b/packages/solid-urql/src/createQuery.ts
@@ -234,6 +234,7 @@ export const createQuery = <
         produce(draft => {
           draft.fetching = false;
           draft.stale = false;
+          draft.hasNext = false;
         })
       );
 
@@ -244,6 +245,7 @@ export const createQuery = <
       produce(draft => {
         draft.fetching = true;
         draft.stale = false;
+        draft.hasNext = false;
       })
     );
 
@@ -255,6 +257,7 @@ export const createQuery = <
             produce(draft => {
               draft.fetching = false;
               draft.stale = false;
+              draft.hasNext = false;
             })
           );
         }),
@@ -268,6 +271,7 @@ export const createQuery = <
                 draft.error = res.error;
                 draft.operation = res.operation;
                 draft.extensions = res.extensions;
+                draft.hasNext = res.hasNext;
               })
             );
           });
diff --git a/packages/svelte-urql/src/mutationStore.ts b/packages/svelte-urql/src/mutationStore.ts
index 6c7c74bf..8e4b2b56 100644
--- a/packages/svelte-urql/src/mutationStore.ts
+++ b/packages/svelte-urql/src/mutationStore.ts
@@ -107,13 +107,14 @@ export function mutationStore<
   const subscription = pipe(
     pipe(
       args.client.executeRequestOperation(operation),
-      map(({ stale, data, error, extensions, operation }) => ({
+      map(({ stale, data, error, extensions, operation, hasNext }) => ({
         fetching: false,
         stale,
         data,
         error,
         operation,
         extensions,
+        hasNext,
       }))
     ),
     scan(
diff --git a/packages/svelte-urql/src/queryStore.ts b/packages/svelte-urql/src/queryStore.ts
index 646a9f41..0c29b74b 100644
--- a/packages/svelte-urql/src/queryStore.ts
+++ b/packages/svelte-urql/src/queryStore.ts
@@ -156,19 +156,29 @@ export function queryStore<
             fromStore(operation$),
             switchMap(operation => {
               return concat<Partial<OperationResultState<Data, Variables>>>([
-                fromValue({ fetching: true, stale: false }),
+                fromValue({ fetching: true, stale: false, hasNext: false }),
                 pipe(
                   args.client.executeRequestOperation(operation),
-                  map(({ stale, data, error, extensions, operation }) => ({
-                    fetching: false,
-                    stale: !!stale,
-                    data,
-                    error,
-                    operation,
-                    extensions,
-                  }))
+                  map(
+                    ({
+                      stale,
+                      data,
+                      error,
+                      extensions,
+                      operation,
+                      hasNext,
+                    }) => ({
+                      fetching: false,
+                      stale: !!stale,
+                      data,
+                      error,
+                      operation,
+                      extensions,
+                      hasNext,
+                    })
+                  )
                 ),
-                fromValue({ fetching: false }),
+                fromValue({ fetching: false, hasNext: false }),
               ]);
             })
           );
diff --git a/packages/vue-urql/src/useMutation.ts b/packages/vue-urql/src/useMutation.ts
index e5856783..15af6982 100644
--- a/packages/vue-urql/src/useMutation.ts
+++ b/packages/vue-urql/src/useMutation.ts
@@ -57,6 +57,8 @@ export interface UseMutationResponse<T, V extends AnyVariables = AnyVariables> {
    * last `Operation` that the current state was for.
    */
   operation: Ref<Operation<T, V> | undefined>;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: Ref<boolean>;
   /** Triggers {@link useMutation} to execute its GraphQL mutation operation.
    *
    * @param variables - variables using which the mutation will be executed.
@@ -135,10 +137,8 @@ export function callUseMutation<T = any, V extends AnyVariables = AnyVariables>(
 ): UseMutationResponse<T, V> {
   const data: Ref<T | undefined> = shallowRef();
 
-  const { fetching, operation, extensions, stale, error } = useRequestState<
-    T,
-    V
-  >();
+  const { fetching, operation, extensions, stale, error, hasNext } =
+    useRequestState<T, V>();
 
   return {
     data,
@@ -147,6 +147,7 @@ export function callUseMutation<T = any, V extends AnyVariables = AnyVariables>(
     error,
     operation,
     extensions,
+    hasNext,
     executeMutation(
       variables: V,
       context?: Partial<OperationContext>
@@ -165,6 +166,7 @@ export function callUseMutation<T = any, V extends AnyVariables = AnyVariables>(
           error.value = result.error;
           operation.value = result.operation;
           extensions.value = result.extensions;
+          hasNext.value = result.hasNext;
         }),
         filter(result => !result.hasNext),
         take(1),
diff --git a/packages/vue-urql/src/useQuery.ts b/packages/vue-urql/src/useQuery.ts
index d8da69b7..e49e38ba 100644
--- a/packages/vue-urql/src/useQuery.ts
+++ b/packages/vue-urql/src/useQuery.ts
@@ -138,6 +138,8 @@ export interface UseQueryState<T = any, V extends AnyVariables = AnyVariables> {
    * documentation on the `pause` option.
    */
   isPaused: Ref<boolean>;
+  /** The {@link OperationResult.hasNext} for the executed query. */
+  hasNext: Ref<boolean>;
   /** Resumes {@link useQuery} if it’s currently paused.
    *
    * @remarks
@@ -241,10 +243,8 @@ export function callUseQuery<T = any, V extends AnyVariables = AnyVariables>(
 ): UseQueryResponse<T, V> {
   const data: Ref<T | undefined> = shallowRef();
 
-  const { fetching, operation, extensions, stale, error } = useRequestState<
-    T,
-    V
-  >();
+  const { fetching, operation, extensions, stale, error, hasNext } =
+    useRequestState<T, V>();
 
   const { isPaused, source, pause, resume, execute, teardown } = useClientState(
     args,
@@ -264,6 +264,7 @@ export function callUseQuery<T = any, V extends AnyVariables = AnyVariables>(
             onEnd(() => {
               fetching.value = false;
               stale.value = false;
+              hasNext.value = false;
             }),
             subscribe(res => {
               data.value = res.data;
@@ -272,12 +273,14 @@ export function callUseQuery<T = any, V extends AnyVariables = AnyVariables>(
               error.value = res.error;
               operation.value = res.operation;
               extensions.value = res.extensions;
+              hasNext.value = res.hasNext;
             })
           ).unsubscribe
         );
       } else {
         fetching.value = false;
         stale.value = false;
+        hasNext.value = false;
       }
     },
     {
@@ -321,6 +324,7 @@ export function callUseQuery<T = any, V extends AnyVariables = AnyVariables>(
     extensions,
     fetching,
     isPaused,
+    hasNext,
     pause,
     resume,
     executeQuery(opts?: Partial<OperationContext>): UseQueryResponse<T, V> {
diff --git a/packages/vue-urql/src/utils.ts b/packages/vue-urql/src/utils.ts
index f7aa49d6..1b0173ab 100644
--- a/packages/vue-urql/src/utils.ts
+++ b/packages/vue-urql/src/utils.ts
@@ -90,12 +90,14 @@ export const useRequestState = <
   T = any,
   V extends AnyVariables = AnyVariables,
 >() => {
+  const hasNext: Ref<boolean> = ref(false);
   const stale: Ref<boolean> = ref(false);
   const fetching: Ref<boolean> = ref(false);
   const error: Ref<CombinedError | undefined> = shallowRef();
   const operation: Ref<Operation<T, V> | undefined> = shallowRef();
   const extensions: Ref<Record<string, any> | undefined> = shallowRef();
   return {
+    hasNext,
     stale,
     fetching,
     error,


Test patch:
diff --git a/packages/preact-urql/src/hooks/useSubscription.test.tsx b/packages/preact-urql/src/hooks/useSubscription.test.tsx
index e255ec08..fd3ae4da 100644
--- a/packages/preact-urql/src/hooks/useSubscription.test.tsx
+++ b/packages/preact-urql/src/hooks/useSubscription.test.tsx
@@ -106,6 +106,7 @@ describe('useSubscription', () => {
       );
       expect(state).toEqual({
         ...data,
+        hasNext: false,
         extensions: undefined,
         fetching: true,
         stale: false,
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 d40f6110..3113e91b 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 @@ exports[`on initial useEffect > initialises default state 1`] = `
   "error": undefined,
   "extensions": undefined,
   "fetching": false,
+  "hasNext": 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 442ec102..98b04193 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 @@ exports[`on initial useEffect > initialises default state 1`] = `
   "error": undefined,
   "extensions": undefined,
   "fetching": true,
+  "hasNext": false,
   "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 442ec102..98b04193 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 @@ exports[`on initial useEffect > initialises default state 1`] = `
   "error": undefined,
   "extensions": undefined,
   "fetching": true,
+  "hasNext": false,
   "operation": undefined,
   "stale": false,
 }