react-relay 16.1.0 → 17.0.0

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 (80) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayFragmentContainer.js.flow +8 -6
  3. package/ReactRelayLocalQueryRenderer.js.flow +4 -1
  4. package/ReactRelayPaginationContainer.js.flow +2 -0
  5. package/ReactRelayQueryRenderer.js.flow +1 -1
  6. package/ReactRelayTypes.js.flow +1 -0
  7. package/buildReactRelayContainer.js.flow +5 -3
  8. package/getRootVariablesForFragments.js.flow +1 -0
  9. package/hooks.js +1 -1
  10. package/hooks.js.flow +1 -1
  11. package/index.js +1 -1
  12. package/index.js.flow +1 -1
  13. package/legacy.js +1 -1
  14. package/lib/ReactRelayFragmentContainer.js +2 -2
  15. package/lib/buildReactRelayContainer.js +3 -3
  16. package/lib/relay-hooks/SuspenseResource.js +7 -4
  17. package/lib/relay-hooks/{FragmentResource.js → legacy/FragmentResource.js} +19 -20
  18. package/lib/relay-hooks/{useBlockingPaginationFragment.js → legacy/useBlockingPaginationFragment.js} +2 -2
  19. package/lib/relay-hooks/{useFragmentNode.js → legacy/useFragmentNode.js} +2 -2
  20. package/lib/relay-hooks/{useRefetchableFragmentNode.js → legacy/useRefetchableFragmentNode.js} +8 -8
  21. package/lib/relay-hooks/{experimental/readFragmentInternal_EXPERIMENTAL.js → readFragmentInternal.js} +7 -5
  22. package/lib/relay-hooks/useFragment.js +3 -13
  23. package/lib/relay-hooks/{experimental/useFragmentInternal_EXPERIMENTAL.js → useFragmentInternal.js} +67 -31
  24. package/lib/relay-hooks/useLazyLoadQueryNode.js +2 -13
  25. package/lib/relay-hooks/usePaginationFragment.js +17 -13
  26. package/lib/relay-hooks/useRefetchableFragment.js +3 -12
  27. package/lib/relay-hooks/{experimental/useRefetchableFragmentInternal_EXPERIMENTAL.js → useRefetchableFragmentInternal.js} +7 -7
  28. package/multi-actor/useRelayActorEnvironment.js.flow +1 -1
  29. package/package.json +2 -2
  30. package/react-relay-hooks.js +2 -2
  31. package/react-relay-hooks.min.js +2 -2
  32. package/react-relay-legacy.js +2 -2
  33. package/react-relay-legacy.min.js +2 -2
  34. package/react-relay.js +2 -2
  35. package/react-relay.min.js +2 -2
  36. package/relay-hooks/EntryPointTypes.flow.js.flow +22 -27
  37. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +14 -1
  38. package/relay-hooks/NestedRelayEntryPointBuilderUtils.js.flow +5 -11
  39. package/relay-hooks/SuspenseResource.js.flow +11 -8
  40. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +22 -1
  41. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +19 -0
  42. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +22 -0
  43. package/relay-hooks/{FragmentResource.js.flow → legacy/FragmentResource.js.flow} +21 -21
  44. package/relay-hooks/{useBlockingPaginationFragment.js.flow → legacy/useBlockingPaginationFragment.js.flow} +6 -6
  45. package/relay-hooks/{useFragmentNode.js.flow → legacy/useFragmentNode.js.flow} +3 -3
  46. package/relay-hooks/{useRefetchableFragmentNode.js.flow → legacy/useRefetchableFragmentNode.js.flow} +9 -9
  47. package/relay-hooks/loadQuery.js.flow +9 -8
  48. package/relay-hooks/{experimental/readFragmentInternal_EXPERIMENTAL.js.flow → readFragmentInternal.js.flow} +8 -4
  49. package/relay-hooks/useClientQuery.js.flow +1 -1
  50. package/relay-hooks/useEntryPointLoader.js.flow +1 -1
  51. package/relay-hooks/useFetchTrackingRef.js.flow +1 -1
  52. package/relay-hooks/useFragment.js.flow +16 -22
  53. package/relay-hooks/{experimental/useFragmentInternal_EXPERIMENTAL.js.flow → useFragmentInternal.js.flow} +71 -19
  54. package/relay-hooks/useIsMountedRef.js.flow +1 -1
  55. package/relay-hooks/useIsOperationNodeActive.js.flow +1 -1
  56. package/relay-hooks/useIsParentQueryActive.js.flow +5 -2
  57. package/relay-hooks/useLazyLoadQuery.js.flow +3 -2
  58. package/relay-hooks/useLazyLoadQueryNode.js.flow +3 -19
  59. package/relay-hooks/useLoadMoreFunction.js.flow +1 -1
  60. package/relay-hooks/useMemoOperationDescriptor.js.flow +1 -1
  61. package/relay-hooks/useMemoVariables.js.flow +1 -1
  62. package/relay-hooks/useMutation.js.flow +1 -1
  63. package/relay-hooks/usePaginationFragment.js.flow +62 -50
  64. package/relay-hooks/usePreloadedQuery.js.flow +2 -1
  65. package/relay-hooks/useQueryLoader.js.flow +2 -5
  66. package/relay-hooks/useRefetchableFragment.js.flow +7 -37
  67. package/relay-hooks/{experimental/useRefetchableFragmentInternal_EXPERIMENTAL.js.flow → useRefetchableFragmentInternal.js.flow} +11 -11
  68. package/relay-hooks/useRelayEnvironment.js.flow +1 -1
  69. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +3 -1
  70. package/relay-hooks/useSubscribeToInvalidationState.js.flow +1 -1
  71. package/relay-hooks/useSubscription.js.flow +1 -1
  72. package/relay-hooks/useUnsafeRef_DEPRECATED.js.flow +1 -1
  73. package/lib/relay-hooks/HooksImplementation.js +0 -15
  74. package/lib/relay-hooks/experimental/useFragment_EXPERIMENTAL.js +0 -26
  75. package/lib/relay-hooks/experimental/usePaginationFragment_EXPERIMENTAL.js +0 -127
  76. package/lib/relay-hooks/experimental/useRefetchableFragment_EXPERIMENTAL.js +0 -23
  77. package/relay-hooks/HooksImplementation.js.flow +0 -45
  78. package/relay-hooks/experimental/useFragment_EXPERIMENTAL.js.flow +0 -66
  79. package/relay-hooks/experimental/usePaginationFragment_EXPERIMENTAL.js.flow +0 -161
  80. package/relay-hooks/experimental/useRefetchableFragment_EXPERIMENTAL.js.flow +0 -49
@@ -11,7 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {QueryResult} from '../QueryResource';
14
+ import type {QueryResult} from './QueryResource';
15
15
  import type {
16
16
  CacheConfig,
17
17
  FetchPolicy,
@@ -26,12 +26,12 @@ import type {
26
26
  MissingLiveResolverField,
27
27
  } from 'relay-runtime/store/RelayStoreTypes';
28
28
 
29
- const {getQueryResourceForEnvironment} = require('../QueryResource');
30
- const useRelayEnvironment = require('../useRelayEnvironment');
29
+ const {getQueryResourceForEnvironment} = require('./QueryResource');
30
+ const useRelayEnvironment = require('./useRelayEnvironment');
31
31
  const invariant = require('invariant');
32
32
  const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
33
33
  const {
34
- __internal: {fetchQuery: fetchQueryInternal},
34
+ __internal: {fetchQuery: fetchQueryInternal, getPromiseForActiveRequest},
35
35
  RelayFeatureFlags,
36
36
  areEqualSelectors,
37
37
  createOperationDescriptor,
@@ -117,6 +117,8 @@ function handlePotentialSnapshotErrorsForState(
117
117
  environment,
118
118
  state.snapshot.missingRequiredFields,
119
119
  state.snapshot.relayResolverErrors,
120
+ state.snapshot.errorResponseFields,
121
+ state.snapshot.selector.node.metadata?.throwOnFieldError ?? false,
120
122
  );
121
123
  } else if (state.kind === 'plural') {
122
124
  for (const snapshot of state.snapshots) {
@@ -124,6 +126,8 @@ function handlePotentialSnapshotErrorsForState(
124
126
  environment,
125
127
  snapshot.missingRequiredFields,
126
128
  snapshot.relayResolverErrors,
129
+ snapshot.errorResponseFields,
130
+ snapshot.selector.node.metadata?.throwOnFieldError ?? false,
127
131
  );
128
132
  }
129
133
  }
@@ -162,6 +166,7 @@ function handleMissedUpdates(
162
166
  selector: currentSnapshot.selector,
163
167
  missingRequiredFields: currentSnapshot.missingRequiredFields,
164
168
  relayResolverErrors: currentSnapshot.relayResolverErrors,
169
+ errorResponseFields: currentSnapshot.errorResponseFields,
165
170
  };
166
171
  return [
167
172
  updatedData !== state.snapshot.data,
@@ -187,6 +192,7 @@ function handleMissedUpdates(
187
192
  selector: currentSnapshot.selector,
188
193
  missingRequiredFields: currentSnapshot.missingRequiredFields,
189
194
  relayResolverErrors: currentSnapshot.relayResolverErrors,
195
+ errorResponseFields: currentSnapshot.errorResponseFields,
190
196
  };
191
197
  if (updatedData !== snapshot.data) {
192
198
  didMissUpdates = true;
@@ -214,7 +220,7 @@ function handleMissingClientEdge(
214
220
  parentFragmentRef: mixed,
215
221
  missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
216
222
  queryOptions?: FragmentQueryOptions,
217
- ): QueryResult {
223
+ ): [QueryResult, ?Promise<mixed>] {
218
224
  const originalVariables = getVariablesFromFragment(
219
225
  parentFragmentNode,
220
226
  parentFragmentRef,
@@ -233,17 +239,23 @@ function handleMissingClientEdge(
233
239
  // according to the component mount/suspense cycle; QueryResource
234
240
  // already handles this by itself.
235
241
  const QueryResource = getQueryResourceForEnvironment(environment);
236
- return QueryResource.prepare(
242
+ const queryResult = QueryResource.prepare(
237
243
  queryOperationDescriptor,
238
244
  fetchQueryInternal(environment, queryOperationDescriptor),
239
245
  queryOptions?.fetchPolicy,
240
246
  );
247
+
248
+ return [
249
+ queryResult,
250
+ getPromiseForActiveRequest(environment, queryOperationDescriptor.request),
251
+ ];
241
252
  }
242
253
 
243
254
  function subscribeToSnapshot(
244
255
  environment: IEnvironment,
245
256
  state: FragmentState,
246
257
  setState: StateUpdaterFunction<FragmentState>,
258
+ hasPendingStateChanges: {current: boolean},
247
259
  ): () => void {
248
260
  if (state.kind === 'bailout') {
249
261
  return () => {};
@@ -264,11 +276,14 @@ function subscribeToSnapshot(
264
276
  name: 'useFragment.subscription.missedUpdates',
265
277
  hasDataChanges: dataChanged,
266
278
  });
279
+ hasPendingStateChanges.current = dataChanged;
267
280
  return dataChanged ? nextState : prevState;
268
281
  } else {
269
282
  return prevState;
270
283
  }
271
284
  }
285
+
286
+ hasPendingStateChanges.current = true;
272
287
  return {
273
288
  kind: 'singular',
274
289
  snapshot: latestSnapshot,
@@ -297,6 +312,8 @@ function subscribeToSnapshot(
297
312
  name: 'useFragment.subscription.missedUpdates',
298
313
  hasDataChanges: dataChanged,
299
314
  });
315
+ hasPendingStateChanges.current =
316
+ hasPendingStateChanges.current || dataChanged;
300
317
  return dataChanged ? nextState : prevState;
301
318
  } else {
302
319
  return prevState;
@@ -304,6 +321,7 @@ function subscribeToSnapshot(
304
321
  }
305
322
  const updated = [...prevState.snapshots];
306
323
  updated[index] = latestSnapshot;
324
+ hasPendingStateChanges.current = true;
307
325
  return {
308
326
  kind: 'plural',
309
327
  snapshots: updated,
@@ -344,7 +362,7 @@ function getFragmentState(
344
362
  }
345
363
 
346
364
  // fragmentNode cannot change during the lifetime of the component, though fragmentRef may change.
347
- function useFragmentInternal_EXPERIMENTAL(
365
+ hook useFragmentInternal(
348
366
  fragmentNode: ReaderFragment,
349
367
  fragmentRef: mixed,
350
368
  hookDisplayName: string,
@@ -450,29 +468,38 @@ function useFragmentInternal_EXPERIMENTAL(
450
468
  // a static (constant) property of the fragment. In practice, this effect will
451
469
  // always or never run for a given invocation of this hook.
452
470
  // eslint-disable-next-line react-hooks/rules-of-hooks
453
- const clientEdgeQueries = useMemo(() => {
471
+ // $FlowFixMe[react-rule-hook]
472
+ const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
454
473
  const missingClientEdges = getMissingClientEdges(state);
455
474
  // eslint-disable-next-line no-shadow
456
475
  let clientEdgeQueries;
476
+ const activeRequestPromises = [];
457
477
  if (missingClientEdges?.length) {
458
478
  clientEdgeQueries = ([]: Array<QueryResult>);
459
479
  for (const edge of missingClientEdges) {
460
- clientEdgeQueries.push(
461
- handleMissingClientEdge(
462
- environment,
463
- fragmentNode,
464
- fragmentRef,
465
- edge,
466
- queryOptions,
467
- ),
480
+ const [queryResult, requestPromise] = handleMissingClientEdge(
481
+ environment,
482
+ fragmentNode,
483
+ fragmentRef,
484
+ edge,
485
+ queryOptions,
468
486
  );
487
+ clientEdgeQueries.push(queryResult);
488
+ if (requestPromise != null) {
489
+ activeRequestPromises.push(requestPromise);
490
+ }
469
491
  }
470
492
  }
471
- return clientEdgeQueries;
493
+ return [clientEdgeQueries, activeRequestPromises];
472
494
  }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
473
495
 
496
+ if (activeRequestPromises.length) {
497
+ throw Promise.all(activeRequestPromises);
498
+ }
499
+
474
500
  // See above note
475
501
  // eslint-disable-next-line react-hooks/rules-of-hooks
502
+ // $FlowFixMe[react-rule-hook]
476
503
  useEffect(() => {
477
504
  const QueryResource = getQueryResourceForEnvironment(environment);
478
505
  if (clientEdgeQueries?.length) {
@@ -505,7 +532,10 @@ function useFragmentInternal_EXPERIMENTAL(
505
532
  // We only suspend when the component is first trying to mount or changing
506
533
  // selectors, not if data becomes missing later:
507
534
  if (
535
+ RelayFeatureFlags.ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE ||
536
+ environment !== previousEnvironment ||
508
537
  !committedFragmentSelectorRef.current ||
538
+ // $FlowFixMe[react-rule-unsafe-ref]
509
539
  !areEqualSelectors(committedFragmentSelectorRef.current, fragmentSelector)
510
540
  ) {
511
541
  invariant(fragmentSelector != null, 'refinement, see invariants above');
@@ -528,6 +558,8 @@ function useFragmentInternal_EXPERIMENTAL(
528
558
  // they're missing even though we are out of options for possibly fetching them:
529
559
  handlePotentialSnapshotErrorsForState(environment, state);
530
560
 
561
+ const hasPendingStateChanges = useRef<boolean>(false);
562
+
531
563
  useEffect(() => {
532
564
  // Check for updates since the state was rendered
533
565
  let currentState = subscribedState;
@@ -546,9 +578,27 @@ function useFragmentInternal_EXPERIMENTAL(
546
578
  }
547
579
  currentState = updatedState;
548
580
  }
549
- return subscribeToSnapshot(environment, currentState, setState);
581
+ return subscribeToSnapshot(
582
+ environment,
583
+ currentState,
584
+ setState,
585
+ hasPendingStateChanges,
586
+ );
550
587
  }, [environment, subscribedState]);
551
588
 
589
+ if (hasPendingStateChanges.current) {
590
+ const updates = handleMissedUpdates(environment, state);
591
+ if (updates != null) {
592
+ const [hasStateUpdates, updatedState] = updates;
593
+ if (hasStateUpdates) {
594
+ setState(updatedState);
595
+ state = updatedState;
596
+ }
597
+ }
598
+ // $FlowFixMe[react-rule-unsafe-ref]
599
+ hasPendingStateChanges.current = false;
600
+ }
601
+
552
602
  let data: ?SelectorData | Array<?SelectorData>;
553
603
  if (isPlural) {
554
604
  // Plural fragments require allocating an array of the snapshot data values,
@@ -558,6 +608,7 @@ function useFragmentInternal_EXPERIMENTAL(
558
608
  // for a particular useFragment invocation site
559
609
  const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
560
610
  // eslint-disable-next-line react-hooks/rules-of-hooks
611
+ // $FlowFixMe[react-rule-hook]
561
612
  data = useMemo(() => {
562
613
  if (state.kind === 'bailout') {
563
614
  // Bailout state can happen if the fragmentRef is a plural array that is empty or has no
@@ -608,10 +659,11 @@ function useFragmentInternal_EXPERIMENTAL(
608
659
 
609
660
  if (__DEV__) {
610
661
  // eslint-disable-next-line react-hooks/rules-of-hooks
662
+ // $FlowFixMe[react-rule-hook]
611
663
  useDebugValue({fragment: fragmentNode.name, data});
612
664
  }
613
665
 
614
666
  return data;
615
667
  }
616
668
 
617
- module.exports = useFragmentInternal_EXPERIMENTAL;
669
+ module.exports = useFragmentInternal;
@@ -13,7 +13,7 @@
13
13
 
14
14
  const {useEffect, useRef} = require('react');
15
15
 
16
- function useIsMountedRef(): {current: boolean} {
16
+ hook useIsMountedRef(): {current: boolean} {
17
17
  const isMountedRef = useRef(true);
18
18
 
19
19
  useEffect(() => {
@@ -23,7 +23,7 @@ const {
23
23
 
24
24
  const {useEffect, useState, useMemo} = React;
25
25
 
26
- function useIsOperationNodeActive(
26
+ hook useIsOperationNodeActive(
27
27
  fragmentNode: ReaderFragment,
28
28
  fragmentRef: mixed,
29
29
  ): boolean {
@@ -17,9 +17,12 @@ const useIsOperationNodeActive = require('./useIsOperationNodeActive');
17
17
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
18
18
  const {getFragment} = require('relay-runtime');
19
19
 
20
- function useIsParentQueryActive<
20
+ hook useIsParentQueryActive<
21
21
  TKey: ?{+$data?: mixed, +$fragmentSpreads: FragmentType, ...},
22
- >(fragmentInput: GraphQLTaggedNode, fragmentRef: TKey): boolean {
22
+ >(
23
+ fragmentInput: GraphQLTaggedNode,
24
+ fragmentRef: TKey,
25
+ ): boolean {
23
26
  const fragmentNode = getFragment(fragmentInput);
24
27
  useStaticFragmentNodeWarning(
25
28
  fragmentNode,
@@ -30,7 +30,7 @@ const {
30
30
  // This separate type export is only needed as long as we are injecting
31
31
  // a separate hooks implementation in ./HooksImplementation -- it can
32
32
  // be removed after we stop doing that.
33
- export type UseLazyLoadQueryHookType = <TVariables: Variables, TData>(
33
+ export type UseLazyLoadQueryHookType = hook <TVariables: Variables, TData>(
34
34
  gqlQuery: Query<TVariables, TData>,
35
35
  variables: TVariables,
36
36
  options?: {
@@ -41,7 +41,7 @@ export type UseLazyLoadQueryHookType = <TVariables: Variables, TData>(
41
41
  },
42
42
  ) => TData;
43
43
 
44
- function useLazyLoadQuery<TVariables: Variables, TData>(
44
+ hook useLazyLoadQuery<TVariables: Variables, TData>(
45
45
  gqlQuery: Query<TVariables, TData>,
46
46
  variables: TVariables,
47
47
  options?: {
@@ -75,4 +75,5 @@ function useLazyLoadQuery<TVariables: Variables, TData>(
75
75
  return data;
76
76
  }
77
77
 
78
+ // $FlowFixMe[react-rule-hook-incompatible]
78
79
  module.exports = (useLazyLoadQuery: UseLazyLoadQueryHookType);
@@ -19,22 +19,20 @@ import type {
19
19
  OperationType,
20
20
  RenderPolicy,
21
21
  } from 'relay-runtime';
22
- import type {ReaderFragment} from 'relay-runtime/util/ReaderNode';
23
22
 
24
- const HooksImplementation = require('./HooksImplementation');
25
23
  const ProfilerContext = require('./ProfilerContext');
26
24
  const {
27
25
  getQueryCacheIdentifier,
28
26
  getQueryResourceForEnvironment,
29
27
  } = require('./QueryResource');
30
28
  const useFetchTrackingRef = require('./useFetchTrackingRef');
31
- const useFragmentNode = require('./useFragmentNode');
29
+ const useFragmentInternal = require('./useFragmentInternal');
32
30
  const useRelayEnvironment = require('./useRelayEnvironment');
33
31
  const React = require('react');
34
32
 
35
33
  const {useContext, useEffect, useState, useRef} = React;
36
34
 
37
- function useLazyLoadQueryNode<TQuery: OperationType>({
35
+ hook useLazyLoadQueryNode<TQuery: OperationType>({
38
36
  query,
39
37
  componentDisplayName,
40
38
  fetchObservable,
@@ -127,7 +125,7 @@ function useLazyLoadQueryNode<TQuery: OperationType>({
127
125
  });
128
126
 
129
127
  const {fragmentNode, fragmentRef} = preparedQueryResult;
130
- const data = useFragmentNodeImpl(
128
+ const data = useFragmentInternal(
131
129
  fragmentNode,
132
130
  fragmentRef,
133
131
  componentDisplayName,
@@ -135,18 +133,4 @@ function useLazyLoadQueryNode<TQuery: OperationType>({
135
133
  return data;
136
134
  }
137
135
 
138
- function useFragmentNodeImpl(
139
- fragment: ReaderFragment,
140
- key: mixed,
141
- componentDisplayName: string,
142
- ): mixed {
143
- const impl = HooksImplementation.get();
144
- if (impl && impl.useFragment__internal) {
145
- return impl.useFragment__internal(fragment, key, componentDisplayName);
146
- } else {
147
- const {data} = useFragmentNode<mixed>(fragment, key, componentDisplayName);
148
- return data;
149
- }
150
- }
151
-
152
136
  module.exports = useLazyLoadQueryNode;
@@ -61,7 +61,7 @@ export type UseLoadMoreFunctionArgs = {
61
61
  onReset: () => void,
62
62
  };
63
63
 
64
- function useLoadMoreFunction<TVariables: Variables>(
64
+ hook useLoadMoreFunction<TVariables: Variables>(
65
65
  args: UseLoadMoreFunctionArgs,
66
66
  ): [LoadMoreFn<TVariables>, boolean, () => void] {
67
67
  const {
@@ -24,7 +24,7 @@ const {createOperationDescriptor, getRequest} = require('relay-runtime');
24
24
 
25
25
  const {useMemo} = React;
26
26
 
27
- function useMemoOperationDescriptor(
27
+ hook useMemoOperationDescriptor(
28
28
  gqlQuery: GraphQLTaggedNode,
29
29
  variables: Variables,
30
30
  cacheConfig?: ?CacheConfig,
@@ -21,7 +21,7 @@ const {useState} = require('react');
21
21
  * This is useful when a `variables` object is used as a value in a depencency
22
22
  * array as it might often be constructed during render.
23
23
  */
24
- function useMemoVariables<TVariables: Variables | null>(
24
+ hook useMemoVariables<TVariables: Variables | null>(
25
25
  variables: TVariables,
26
26
  ): TVariables {
27
27
  const [mirroredVariables, setMirroredVariables] = useState(variables);
@@ -64,7 +64,7 @@ type UseMutationConfigInternal<TVariables, TData, TRawResponse> = {
64
64
  variables: TVariables,
65
65
  };
66
66
 
67
- function useMutation<TVariables: Variables, TData, TRawResponse = {...}>(
67
+ hook useMutation<TVariables: Variables, TData, TRawResponse = {...}>(
68
68
  mutation: Mutation<TVariables, TData, TRawResponse>,
69
69
  commitMutationFn?: (
70
70
  environment: IEnvironment,
@@ -12,9 +12,9 @@
12
12
  'use strict';
13
13
 
14
14
  import type {LoadMoreFn, UseLoadMoreFunctionArgs} from './useLoadMoreFunction';
15
- import type {RefetchFn} from './useRefetchableFragment';
16
- import type {Options} from './useRefetchableFragmentNode';
15
+ import type {Options} from './useRefetchableFragmentInternal';
17
16
  import type {
17
+ Disposable,
18
18
  FragmentType,
19
19
  GraphQLResponse,
20
20
  Observer,
@@ -22,9 +22,9 @@ import type {
22
22
  Variables,
23
23
  } from 'relay-runtime';
24
24
 
25
- const HooksImplementation = require('./HooksImplementation');
26
25
  const useLoadMoreFunction = require('./useLoadMoreFunction');
27
- const useRefetchableFragmentNode = require('./useRefetchableFragmentNode');
26
+ const useRefetchableFragmentInternal = require('./useRefetchableFragmentInternal');
27
+ const useRelayEnvironment = require('./useRelayEnvironment');
28
28
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
29
29
  const {useCallback, useDebugValue, useState} = require('react');
30
30
  const {
@@ -33,6 +33,40 @@ const {
33
33
  getPaginationMetadata,
34
34
  } = require('relay-runtime');
35
35
 
36
+ type RefetchVariables<TVariables, TKey: ?{+$fragmentSpreads: mixed, ...}> =
37
+ // NOTE: This type ensures that the type of the returned variables is either:
38
+ // - nullable if the provided ref type is nullable
39
+ // - non-nullable if the provided ref type is non-nullable
40
+ [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}]
41
+ ? Partial<TVariables>
42
+ : TVariables;
43
+
44
+ type RefetchFnBase<TVars, TOptions> = (
45
+ vars: TVars,
46
+ options?: TOptions,
47
+ ) => Disposable;
48
+
49
+ type RefetchFn<TVariables, TKey, TOptions = Options> = RefetchFnBase<
50
+ RefetchVariables<TVariables, TKey>,
51
+ TOptions,
52
+ >;
53
+
54
+ export type ReturnType<TVariables, TData, TKey> = {
55
+ // NOTE: This type ensures that the type of the returned data is either:
56
+ // - nullable if the provided ref type is nullable
57
+ // - non-nullable if the provided ref type is non-nullable
58
+ data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}]
59
+ ? TData
60
+ : ?TData,
61
+ loadNext: LoadMoreFn<TVariables>,
62
+ loadPrevious: LoadMoreFn<TVariables>,
63
+ hasNext: boolean,
64
+ hasPrevious: boolean,
65
+ isLoadingNext: boolean,
66
+ isLoadingPrevious: boolean,
67
+ refetch: RefetchFn<TVariables, TKey>,
68
+ };
69
+
36
70
  // This separate type export is only needed as long as we are injecting
37
71
  // a separate hooks implementation in ./HooksImplementation -- it can
38
72
  // be removed after we stop doing that.
@@ -46,7 +80,7 @@ export type UsePaginationFragmentType = <
46
80
  parentFragmentRef: TKey,
47
81
  ) => ReturnType<TVariables, TData, TKey>;
48
82
 
49
- function usePaginationFragment_LEGACY<
83
+ hook usePaginationFragment<
50
84
  TFragmentType: FragmentType,
51
85
  TVariables: Variables,
52
86
  TData,
@@ -65,9 +99,9 @@ function usePaginationFragment_LEGACY<
65
99
  const {connectionPathInFragmentData, paginationRequest, paginationMetadata} =
66
100
  getPaginationMetadata(fragmentNode, componentDisplayName);
67
101
 
68
- const {fragmentData, fragmentRef, refetch} = useRefetchableFragmentNode<
69
- $FlowFixMe,
70
- $FlowFixMe,
102
+ const {fragmentData, fragmentRef, refetch} = useRefetchableFragmentInternal<
103
+ {variables: TVariables, response: TData},
104
+ {data?: TData},
71
105
  >(fragmentNode, parentFragmentRef, componentDisplayName);
72
106
  const fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef);
73
107
 
@@ -99,7 +133,7 @@ function usePaginationFragment_LEGACY<
99
133
  paginationRequest,
100
134
  });
101
135
 
102
- const refetchPagination: RefetchFn<TVariables, TKey> = useCallback(
136
+ const refetchPagination = useCallback(
103
137
  (variables: TVariables, options: void | Options) => {
104
138
  disposeFetchNext();
105
139
  disposeFetchPrevious();
@@ -110,6 +144,7 @@ function usePaginationFragment_LEGACY<
110
144
 
111
145
  if (__DEV__) {
112
146
  // eslint-disable-next-line react-hooks/rules-of-hooks
147
+ // $FlowFixMe[react-rule-hook]
113
148
  useDebugValue({
114
149
  fragment: fragmentNode.name,
115
150
  data: fragmentData,
@@ -120,7 +155,8 @@ function usePaginationFragment_LEGACY<
120
155
  });
121
156
  }
122
157
  return {
123
- data: (fragmentData: $FlowFixMe),
158
+ // $FlowFixMe[incompatible-return]
159
+ data: fragmentData,
124
160
  loadNext,
125
161
  loadPrevious,
126
162
  hasNext,
@@ -131,7 +167,7 @@ function usePaginationFragment_LEGACY<
131
167
  };
132
168
  }
133
169
 
134
- function useLoadMore<TVariables: Variables>(
170
+ hook useLoadMore<TVariables: Variables>(
135
171
  args: $Diff<
136
172
  UseLoadMoreFunctionArgs,
137
173
  {
@@ -141,7 +177,21 @@ function useLoadMore<TVariables: Variables>(
141
177
  },
142
178
  >,
143
179
  ): [LoadMoreFn<TVariables>, boolean, boolean, () => void] {
144
- const [isLoadingMore, setIsLoadingMore] = useState(false);
180
+ const environment = useRelayEnvironment();
181
+ const [isLoadingMore, reallySetIsLoadingMore] = useState(false);
182
+ // Schedule this update since it must be observed by components at the same
183
+ // batch as when hasNext changes. hasNext is read from the store and store
184
+ // updates are scheduled, so this must be scheduled too.
185
+ const setIsLoadingMore = (value: boolean) => {
186
+ const schedule = environment.getScheduler()?.schedule;
187
+ if (schedule) {
188
+ schedule(() => {
189
+ reallySetIsLoadingMore(value);
190
+ });
191
+ } else {
192
+ reallySetIsLoadingMore(value);
193
+ }
194
+ };
145
195
  const observer = {
146
196
  start: () => setIsLoadingMore(true),
147
197
  complete: () => setIsLoadingMore(false),
@@ -156,42 +206,4 @@ function useLoadMore<TVariables: Variables>(
156
206
  return [loadMore, hasMore, isLoadingMore, disposeFetch];
157
207
  }
158
208
 
159
- export type ReturnType<TVariables, TData, TKey> = {
160
- // NOTE: This type ensures that the type of the returned data is either:
161
- // - nullable if the provided ref type is nullable
162
- // - non-nullable if the provided ref type is non-nullable
163
- data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}]
164
- ? TData
165
- : ?TData,
166
- loadNext: LoadMoreFn<TVariables>,
167
- loadPrevious: LoadMoreFn<TVariables>,
168
- hasNext: boolean,
169
- hasPrevious: boolean,
170
- isLoadingNext: boolean,
171
- isLoadingPrevious: boolean,
172
- refetch: RefetchFn<TVariables, TKey>,
173
- };
174
-
175
- function usePaginationFragment<
176
- TFragmentType: FragmentType,
177
- TVariables: Variables,
178
- TData,
179
- TKey: ?{+$fragmentSpreads: TFragmentType, ...},
180
- >(
181
- fragmentInput: RefetchableFragment<TFragmentType, TData, TVariables>,
182
- parentFragmentRef: TKey,
183
- ): ReturnType<TVariables, TData, TKey> {
184
- const impl = HooksImplementation.get();
185
- if (impl) {
186
- // $FlowExpectedError[incompatible-return] Flow cannot prove that two conditional type satisfy each other
187
- return impl.usePaginationFragment<TFragmentType, TVariables, TData, TKey>(
188
- fragmentInput,
189
- parentFragmentRef,
190
- );
191
- } else {
192
- // eslint-disable-next-line react-hooks/rules-of-hooks
193
- return usePaginationFragment_LEGACY(fragmentInput, parentFragmentRef);
194
- }
195
- }
196
-
197
209
  module.exports = usePaginationFragment;
@@ -52,7 +52,7 @@ type PreloadedQuery<
52
52
  TEnvironmentProviderOptions,
53
53
  >;
54
54
 
55
- function usePreloadedQuery<
55
+ hook usePreloadedQuery<
56
56
  TVariables: Variables,
57
57
  TData,
58
58
  TRawResponse: ?{...} = void,
@@ -164,6 +164,7 @@ function usePreloadedQuery<
164
164
 
165
165
  if (__DEV__) {
166
166
  // eslint-disable-next-line react-hooks/rules-of-hooks
167
+ // $FlowFixMe[react-rule-hook]
167
168
  useDebugValue({
168
169
  query: preloadedQuery.name,
169
170
  variables: preloadedQuery.variables,
@@ -108,11 +108,7 @@ declare function useQueryLoader<TQuery: OperationType>(
108
108
  initialQueryReference?: ?PreloadedQuery<TQuery>,
109
109
  ): UseQueryLoaderHookReturnType<TQuery['variables'], TQuery['response']>;
110
110
 
111
- function useQueryLoader<
112
- TVariables: Variables,
113
- TData,
114
- TRawResponse: ?{...} = void,
115
- >(
111
+ hook useQueryLoader<TVariables: Variables, TData, TRawResponse: ?{...} = void>(
116
112
  preloadableRequest: Query<TVariables, TData, TRawResponse>,
117
113
  initialQueryReference?: ?PreloadedQuery<{
118
114
  response: TData,
@@ -172,6 +168,7 @@ function useQueryLoader<
172
168
  // necessary here
173
169
  // TODO(T78446637): Handle disposal of managed query references in
174
170
  // components that were never mounted after rendering
171
+ // $FlowFixMe[react-rule-unsafe-ref]
175
172
  undisposedQueryReferencesRef.current.add(initialQueryReferenceInternal);
176
173
  setPreviousInitialQueryReference(initialQueryReferenceInternal);
177
174
  setQueryReference(initialQueryReferenceInternal);