@trackunit/react-graphql-hooks 1.15.1 → 1.15.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.cjs.js +51 -15
  2. package/index.esm.js +52 -16
  3. package/package.json +4 -4
package/index.cjs.js CHANGED
@@ -220,13 +220,23 @@ const useStableVariablesWithAbort = (variables, skip = false) => {
220
220
  setAbortController(new AbortController());
221
221
  }
222
222
  }
223
- reactComponents.useWatch({
224
- value: variables,
225
- onChange: setStableVariables,
226
- skip: !Boolean(variables),
227
- });
228
223
  return { stableVariables, abortController };
229
224
  };
225
+ /**
226
+ * Extracts declared variable names from a GraphQL document so the hook
227
+ * only injects relay pagination variables the query actually accepts.
228
+ */
229
+ const getQueryVariableNames = (document) => {
230
+ const definition = document.definitions[0];
231
+ if (definition === undefined || definition.kind !== "OperationDefinition") {
232
+ return new Set();
233
+ }
234
+ const varDefs = definition.variableDefinitions;
235
+ if (varDefs === undefined) {
236
+ return new Set();
237
+ }
238
+ return new Set(varDefs.map(v => v.variable.name.value));
239
+ };
230
240
  /**
231
241
  * `usePaginationQuery` fetches data from a GraphQL query with Relay-style cursor pagination.
232
242
  * It manages page accumulation, loading state, and provides `pagination` controls (including `fetchNext`)
@@ -273,6 +283,7 @@ const useStableVariablesWithAbort = (variables, skip = false) => {
273
283
  */
274
284
  const usePaginationQuery = (document, props) => {
275
285
  const pageSize = props.pageSize ?? props.variables?.first ?? reactComponents.defaultPageSize;
286
+ const declaredVarNames = react.useMemo(() => getQueryVariableNames(document), [document]);
276
287
  const { stableVariables, abortController } = useStableVariablesWithAbort(props.variables, props.skip);
277
288
  const [state, dispatch] = react.useReducer(createPaginationReducer(), {
278
289
  data: undefined,
@@ -306,8 +317,10 @@ const usePaginationQuery = (document, props) => {
306
317
  onReset,
307
318
  initialCursor: props.initialCursor,
308
319
  });
320
+ const { skip: _skip, updateQuery: _uq, pageSize: _ps, initialCursor: _ic, variables: _vars, ...lazyQueryProps } = props;
321
+ const networkStatusRef = react.useRef(0);
309
322
  const [, { previousData, fetchMore, networkStatus, loading: lazyLoading }] = useLazyQuery(document, {
310
- ...props,
323
+ ...lazyQueryProps,
311
324
  variables: stableVariables !== undefined
312
325
  ? {
313
326
  ...stableVariables,
@@ -324,16 +337,19 @@ const usePaginationQuery = (document, props) => {
324
337
  },
325
338
  notifyOnNetworkStatusChange: true,
326
339
  onCompleted: completedData => {
327
- if (networkStatus === client.NetworkStatus.refetch) {
340
+ if (networkStatusRef.current === client.NetworkStatus.refetch) {
328
341
  dispatch({ type: "INCREMENT_RESET_TRIGGER" });
329
342
  }
330
343
  onCompletedRef.current?.(completedData);
331
- }, // This is safe since we have no cache
332
- // and without it it will make a magic extra call to the query check this
333
- // https://stackoverflow.com/questions/66441463/fetchmore-request-executed-twice-every-time
334
- nextFetchPolicy: "network-only",
335
- initialFetchPolicy: "network-only",
344
+ },
345
+ // This is safe since we have no cache and want to control fetch outselves
346
+ fetchPolicy: "standby",
347
+ nextFetchPolicy: "standby",
348
+ initialFetchPolicy: "standby",
336
349
  });
350
+ react.useEffect(() => {
351
+ networkStatusRef.current = networkStatus;
352
+ }, [networkStatus]);
337
353
  const executeFetch = react.useCallback((variables, prev, direction) => {
338
354
  if (props.skip) {
339
355
  if (props.initialCursor === undefined) {
@@ -343,7 +359,8 @@ const usePaginationQuery = (document, props) => {
343
359
  }
344
360
  setIsLoading(true);
345
361
  const signal = abortController.signal;
346
- const fetchMoreVariables = { ...stableVariables, ...variables };
362
+ const filteredRelayVars = Object.fromEntries(Object.entries(variables).filter(([key]) => declaredVarNames.has(key)));
363
+ const fetchMoreVariables = { ...stableVariables, ...filteredRelayVars };
347
364
  fetchMore({
348
365
  variables: fetchMoreVariables,
349
366
  updateQuery: createUpdateQueryHandler({
@@ -388,7 +405,16 @@ const usePaginationQuery = (document, props) => {
388
405
  }
389
406
  throw error;
390
407
  });
391
- }, [props.skip, props.initialCursor, stableVariables, setIsLoading, fetchMore, abortController, setPageInfo]);
408
+ }, [
409
+ props.skip,
410
+ props.initialCursor,
411
+ stableVariables,
412
+ setIsLoading,
413
+ fetchMore,
414
+ abortController,
415
+ setPageInfo,
416
+ declaredVarNames,
417
+ ]);
392
418
  // Single ref for values that effects need without triggering re-runs.
393
419
  // Defined after executeFetch so it can be initialized with the real value.
394
420
  const latestRef = react.useRef({
@@ -410,15 +436,25 @@ const usePaginationQuery = (document, props) => {
410
436
  });
411
437
  // Reset accumulated data when variables change (skip until first load
412
438
  // to preserve initialCursor)
439
+ const pendingResetRef = react.useRef(false);
413
440
  react.useEffect(() => {
414
441
  if (!hasLoadedDataRef.current) {
415
442
  return;
416
443
  }
444
+ pendingResetRef.current = true;
417
445
  dispatch({ type: "RESET" });
418
446
  reset();
419
447
  }, [stableVariables, reset]);
420
- // Fetch initial page when variables or reset trigger changes
448
+ // Fetch initial page when variables or reset trigger changes.
449
+ // When stableVariables changes after data is loaded, the reset effect above
450
+ // also increments resetTrigger — which would cause this effect to fire twice.
451
+ // The pendingReset guard skips the first (stableVariables-triggered) fire so
452
+ // only the resetTrigger-triggered fire fetches.
421
453
  react.useEffect(() => {
454
+ if (pendingResetRef.current) {
455
+ pendingResetRef.current = false;
456
+ return;
457
+ }
422
458
  const fetchVariables = {
423
459
  first: latestRef.current.first ?? pageSize,
424
460
  last: undefined,
package/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { registerTranslations } from '@trackunit/i18n-library-translation';
3
3
  import { useLazyQuery as useLazyQuery$1, NetworkStatus, useQuery as useQuery$1 } from '@apollo/client';
4
4
  import { omit, isEqual } from 'es-toolkit';
5
5
  import { useMemo, useReducer, useRef, useEffect, useCallback, useState } from 'react';
6
- import { defaultPageSize, useRelayPagination, useWatch } from '@trackunit/react-components';
6
+ import { defaultPageSize, useRelayPagination } from '@trackunit/react-components';
7
7
  import { truthy, objectKeys } from '@trackunit/shared-utils';
8
8
 
9
9
  var defaultTranslations = {
@@ -218,13 +218,23 @@ const useStableVariablesWithAbort = (variables, skip = false) => {
218
218
  setAbortController(new AbortController());
219
219
  }
220
220
  }
221
- useWatch({
222
- value: variables,
223
- onChange: setStableVariables,
224
- skip: !Boolean(variables),
225
- });
226
221
  return { stableVariables, abortController };
227
222
  };
223
+ /**
224
+ * Extracts declared variable names from a GraphQL document so the hook
225
+ * only injects relay pagination variables the query actually accepts.
226
+ */
227
+ const getQueryVariableNames = (document) => {
228
+ const definition = document.definitions[0];
229
+ if (definition === undefined || definition.kind !== "OperationDefinition") {
230
+ return new Set();
231
+ }
232
+ const varDefs = definition.variableDefinitions;
233
+ if (varDefs === undefined) {
234
+ return new Set();
235
+ }
236
+ return new Set(varDefs.map(v => v.variable.name.value));
237
+ };
228
238
  /**
229
239
  * `usePaginationQuery` fetches data from a GraphQL query with Relay-style cursor pagination.
230
240
  * It manages page accumulation, loading state, and provides `pagination` controls (including `fetchNext`)
@@ -271,6 +281,7 @@ const useStableVariablesWithAbort = (variables, skip = false) => {
271
281
  */
272
282
  const usePaginationQuery = (document, props) => {
273
283
  const pageSize = props.pageSize ?? props.variables?.first ?? defaultPageSize;
284
+ const declaredVarNames = useMemo(() => getQueryVariableNames(document), [document]);
274
285
  const { stableVariables, abortController } = useStableVariablesWithAbort(props.variables, props.skip);
275
286
  const [state, dispatch] = useReducer(createPaginationReducer(), {
276
287
  data: undefined,
@@ -304,8 +315,10 @@ const usePaginationQuery = (document, props) => {
304
315
  onReset,
305
316
  initialCursor: props.initialCursor,
306
317
  });
318
+ const { skip: _skip, updateQuery: _uq, pageSize: _ps, initialCursor: _ic, variables: _vars, ...lazyQueryProps } = props;
319
+ const networkStatusRef = useRef(0);
307
320
  const [, { previousData, fetchMore, networkStatus, loading: lazyLoading }] = useLazyQuery(document, {
308
- ...props,
321
+ ...lazyQueryProps,
309
322
  variables: stableVariables !== undefined
310
323
  ? {
311
324
  ...stableVariables,
@@ -322,16 +335,19 @@ const usePaginationQuery = (document, props) => {
322
335
  },
323
336
  notifyOnNetworkStatusChange: true,
324
337
  onCompleted: completedData => {
325
- if (networkStatus === NetworkStatus.refetch) {
338
+ if (networkStatusRef.current === NetworkStatus.refetch) {
326
339
  dispatch({ type: "INCREMENT_RESET_TRIGGER" });
327
340
  }
328
341
  onCompletedRef.current?.(completedData);
329
- }, // This is safe since we have no cache
330
- // and without it it will make a magic extra call to the query check this
331
- // https://stackoverflow.com/questions/66441463/fetchmore-request-executed-twice-every-time
332
- nextFetchPolicy: "network-only",
333
- initialFetchPolicy: "network-only",
342
+ },
343
+ // This is safe since we have no cache and want to control fetch outselves
344
+ fetchPolicy: "standby",
345
+ nextFetchPolicy: "standby",
346
+ initialFetchPolicy: "standby",
334
347
  });
348
+ useEffect(() => {
349
+ networkStatusRef.current = networkStatus;
350
+ }, [networkStatus]);
335
351
  const executeFetch = useCallback((variables, prev, direction) => {
336
352
  if (props.skip) {
337
353
  if (props.initialCursor === undefined) {
@@ -341,7 +357,8 @@ const usePaginationQuery = (document, props) => {
341
357
  }
342
358
  setIsLoading(true);
343
359
  const signal = abortController.signal;
344
- const fetchMoreVariables = { ...stableVariables, ...variables };
360
+ const filteredRelayVars = Object.fromEntries(Object.entries(variables).filter(([key]) => declaredVarNames.has(key)));
361
+ const fetchMoreVariables = { ...stableVariables, ...filteredRelayVars };
345
362
  fetchMore({
346
363
  variables: fetchMoreVariables,
347
364
  updateQuery: createUpdateQueryHandler({
@@ -386,7 +403,16 @@ const usePaginationQuery = (document, props) => {
386
403
  }
387
404
  throw error;
388
405
  });
389
- }, [props.skip, props.initialCursor, stableVariables, setIsLoading, fetchMore, abortController, setPageInfo]);
406
+ }, [
407
+ props.skip,
408
+ props.initialCursor,
409
+ stableVariables,
410
+ setIsLoading,
411
+ fetchMore,
412
+ abortController,
413
+ setPageInfo,
414
+ declaredVarNames,
415
+ ]);
390
416
  // Single ref for values that effects need without triggering re-runs.
391
417
  // Defined after executeFetch so it can be initialized with the real value.
392
418
  const latestRef = useRef({
@@ -408,15 +434,25 @@ const usePaginationQuery = (document, props) => {
408
434
  });
409
435
  // Reset accumulated data when variables change (skip until first load
410
436
  // to preserve initialCursor)
437
+ const pendingResetRef = useRef(false);
411
438
  useEffect(() => {
412
439
  if (!hasLoadedDataRef.current) {
413
440
  return;
414
441
  }
442
+ pendingResetRef.current = true;
415
443
  dispatch({ type: "RESET" });
416
444
  reset();
417
445
  }, [stableVariables, reset]);
418
- // Fetch initial page when variables or reset trigger changes
446
+ // Fetch initial page when variables or reset trigger changes.
447
+ // When stableVariables changes after data is loaded, the reset effect above
448
+ // also increments resetTrigger — which would cause this effect to fire twice.
449
+ // The pendingReset guard skips the first (stableVariables-triggered) fire so
450
+ // only the resetTrigger-triggered fire fetches.
419
451
  useEffect(() => {
452
+ if (pendingResetRef.current) {
453
+ pendingResetRef.current = false;
454
+ return;
455
+ }
420
456
  const fetchVariables = {
421
457
  first: latestRef.current.first ?? pageSize,
422
458
  last: undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-graphql-hooks",
3
- "version": "1.15.1",
3
+ "version": "1.15.6",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -9,10 +9,10 @@
9
9
  "dependencies": {
10
10
  "@apollo/client": "3.13.8",
11
11
  "react": "19.0.0",
12
- "@trackunit/i18n-library-translation": "1.12.52",
13
- "@trackunit/shared-utils": "1.13.61",
12
+ "@trackunit/i18n-library-translation": "1.12.54",
13
+ "@trackunit/shared-utils": "1.13.63",
14
14
  "es-toolkit": "^1.39.10",
15
- "@trackunit/react-components": "1.18.1"
15
+ "@trackunit/react-components": "1.18.3"
16
16
  },
17
17
  "module": "./index.esm.js",
18
18
  "main": "./index.cjs.js",