react-relay 16.1.0 → 17.0.0

Sign up to get free protection for your applications and to get access to all the features.
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);