1'use client';
2
3import type {
4 AnyVariables,
5 CombinedError,
6 GraphQLRequestParams,
7 Operation,
8 OperationContext,
9 RequestPolicy,
10} from 'urql';
11import { createRequest, useQuery as orig_useQuery } from 'urql';
12import { useUrqlValue } from './useUrqlValue';
13
14/** Input arguments for the {@link useQuery} hook.
15 *
16 * @param query - The GraphQL query that `useQuery` executes.
17 * @param variables - The variables for the GraphQL query that `useQuery` executes.
18 */
19export type UseQueryArgs<
20 Variables extends AnyVariables = AnyVariables,
21 Data = any,
22> = {
23 /** Updates the {@link RequestPolicy} for the executed GraphQL query operation.
24 *
25 * @remarks
26 * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation
27 * that `useQuery` executes, and indicates a caching strategy for cache exchanges.
28 *
29 * For example, when set to `'cache-and-network'`, {@link useQuery} will
30 * receive a cached result with `stale: true` and an API request will be
31 * sent in the background.
32 *
33 * @see {@link OperationContext.requestPolicy} for where this value is set.
34 */
35 requestPolicy?: RequestPolicy;
36 /** Updates the {@link OperationContext} for the executed GraphQL query operation.
37 *
38 * @remarks
39 * `context` may be passed to {@link useQuery}, to update the {@link OperationContext}
40 * of a query operation. This may be used to update the `context` that exchanges
41 * will receive for a single hook.
42 *
43 * Hint: This should be wrapped in a `useMemo` hook, to make sure that your
44 * component doesn’t infinitely update.
45 *
46 * @example
47 * ```ts
48 * const [result, reexecute] = useQuery({
49 * query,
50 * context: useMemo(() => ({
51 * additionalTypenames: ['Item'],
52 * }), [])
53 * });
54 * ```
55 */
56 context?: Partial<OperationContext>;
57 /** Prevents {@link useQuery} from automatically executing GraphQL query operations.
58 *
59 * @remarks
60 * `pause` may be set to `true` to stop {@link useQuery} from executing
61 * automatically. The hook will stop receiving updates from the {@link Client}
62 * and won’t execute the query operation, until either it’s set to `false`
63 * or the {@link UseQueryExecute} function is called.
64 *
65 * @see {@link https://urql.dev/goto/docs/basics/react-preact/#pausing-usequery} for
66 * documentation on the `pause` option.
67 */
68 pause?: boolean;
69} & GraphQLRequestParams<Data, Variables>;
70
71/** State of the current query, your {@link useQuery} hook is executing.
72 *
73 * @remarks
74 * `UseQueryState` is returned (in a tuple) by {@link useQuery} and
75 * gives you the updating {@link OperationResult} of GraphQL queries.
76 *
77 * Even when the query and variables passed to {@link useQuery} change,
78 * this state preserves the prior state and sets the `fetching` flag to
79 * `true`.
80 * This allows you to display the previous state, while implementing
81 * a separate loading indicator separately.
82 */
83export interface UseQueryState<
84 Data = any,
85 Variables extends AnyVariables = AnyVariables,
86> {
87 /** Indicates whether `useQuery` is waiting for a new result.
88 *
89 * @remarks
90 * When `useQuery` is passed a new query and/or variables, it will
91 * start executing the new query operation and `fetching` is set to
92 * `true` until a result arrives.
93 *
94 * Hint: This is subtly different than whether the query is actually
95 * fetching, and doesn’t indicate whether a query is being re-executed
96 * in the background. For this, see {@link UseQueryState.stale}.
97 */
98 fetching: boolean;
99 /** Indicates that the state is not fresh and a new result will follow.
100 *
101 * @remarks
102 * The `stale` flag is set to `true` when a new result for the query
103 * is expected and `useQuery` is waiting for it. This may indicate that
104 * a new request is being requested in the background.
105 *
106 * @see {@link OperationResult.stale} for the source of this value.
107 */
108 stale: boolean;
109 /** The {@link OperationResult.data} for the executed query. */
110 data?: Data;
111 /** The {@link OperationResult.error} for the executed query. */
112 error?: CombinedError;
113 /** The {@link OperationResult.hasNext} for the executed query. */
114 hasNext: boolean;
115 /** The {@link OperationResult.extensions} for the executed query. */
116 extensions?: Record<string, any>;
117 /** The {@link Operation} that the current state is for.
118 *
119 * @remarks
120 * This is the {@link Operation} that is currently being executed.
121 * When {@link UseQueryState.fetching} is `true`, this is the
122 * last `Operation` that the current state was for.
123 */
124 operation?: Operation<Data, Variables>;
125}
126
127/** Triggers {@link useQuery} to execute a new GraphQL query operation.
128 *
129 * @param opts - optionally, context options that will be merged with the hook's
130 * {@link UseQueryArgs.context} options and the `Client`’s options.
131 *
132 * @remarks
133 * When called, {@link useQuery} will re-execute the GraphQL query operation
134 * it currently holds, even if {@link UseQueryArgs.pause} is set to `true`.
135 *
136 * This is useful for executing a paused query or re-executing a query
137 * and get a new network result, by passing a new request policy.
138 *
139 * ```ts
140 * const [result, reexecuteQuery] = useQuery({ query });
141 *
142 * const refresh = () => {
143 * // Re-execute the query with a network-only policy, skipping the cache
144 * reexecuteQuery({ requestPolicy: 'network-only' });
145 * };
146 * ```
147 */
148export type UseQueryExecute = (opts?: Partial<OperationContext>) => void;
149
150/** Result tuple returned by the {@link useQuery} hook.
151 *
152 * @remarks
153 * Similarly to a `useState` hook’s return value,
154 * the first element is the {@link useQuery}’s result and state,
155 * a {@link UseQueryState} object,
156 * and the second is used to imperatively re-execute the query
157 * via a {@link UseQueryExecute} function.
158 */
159export type UseQueryResponse<
160 Data = any,
161 Variables extends AnyVariables = AnyVariables,
162> = [UseQueryState<Data, Variables>, UseQueryExecute];
163
164/** Hook to run a GraphQL query and get updated GraphQL results.
165 *
166 * @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options.
167 * @returns a {@link UseQueryResponse} tuple of a {@link UseQueryState} result, and re-execute function.
168 *
169 * @remarks
170 * `useQuery` allows GraphQL queries to be defined and executed.
171 * Given {@link UseQueryArgs.query}, it executes the GraphQL query with the
172 * context’s {@link Client}.
173 *
174 * The returned result updates when the `Client` has new results
175 * for the query, and changes when your input `args` change.
176 *
177 * Additionally, if the `suspense` option is enabled on the `Client`,
178 * the `useQuery` hook will suspend instead of indicating that it’s
179 * waiting for a result via {@link UseQueryState.fetching}.
180 *
181 * @see {@link https://urql.dev/goto/urql/docs/basics/react-preact/#queries} for `useQuery` docs.
182 *
183 * @example
184 * ```ts
185 * import { gql, useQuery } from 'urql';
186 *
187 * const TodosQuery = gql`
188 * query { todos { id, title } }
189 * `;
190 *
191 * const Todos = () => {
192 * const [result, reexecuteQuery] = useQuery({
193 * query: TodosQuery,
194 * variables: {},
195 * });
196 * // ...
197 * };
198 * ```
199 */
200export function useQuery<
201 Data = any,
202 Variables extends AnyVariables = AnyVariables,
203>(
204 args: UseQueryArgs<Variables, Data>
205): UseQueryResponse<Data, Variables | undefined> {
206 const request = createRequest(
207 args.query,
208 (args.variables || {}) as AnyVariables
209 );
210 useUrqlValue(request.key);
211
212 const [result, execute] = orig_useQuery(args);
213
214 useUrqlValue(request.key);
215
216 return [result, execute];
217}