react-relay 18.2.0 → 20.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 (69) hide show
  1. package/ReactRelayContainerUtils.js.flow +2 -2
  2. package/ReactRelayContext.js +1 -1
  3. package/ReactRelayFragmentContainer.js.flow +5 -7
  4. package/ReactRelayLoggingContext.js.flow +21 -0
  5. package/ReactRelayPaginationContainer.js.flow +8 -8
  6. package/ReactRelayRefetchContainer.js.flow +8 -8
  7. package/ReactRelayTypes.js.flow +18 -11
  8. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +1 -8
  9. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +2 -5
  10. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +2 -5
  11. package/buildReactRelayContainer.js.flow +4 -5
  12. package/hooks.js +1 -1
  13. package/index.js +1 -1
  14. package/index.js.flow +3 -3
  15. package/legacy.js +1 -1
  16. package/lib/ReactRelayLoggingContext.js +6 -0
  17. package/lib/index.js +2 -2
  18. package/lib/relay-hooks/legacy/FragmentResource.js +3 -3
  19. package/lib/relay-hooks/legacy/useRefetchableFragmentNode.js +1 -1
  20. package/lib/relay-hooks/loadEntryPoint.js +3 -0
  21. package/lib/relay-hooks/loadQuery.js +5 -3
  22. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +7 -2
  23. package/lib/relay-hooks/readFragmentInternal.js +3 -3
  24. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +12 -7
  25. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +25 -10
  26. package/lib/relay-hooks/useLoadMoreFunction.js +3 -2
  27. package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +3 -2
  28. package/lib/relay-hooks/usePaginationFragment.js +5 -1
  29. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment.js +228 -0
  30. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +8 -8
  31. package/lib/relay-hooks/useRefetchableFragmentInternal.js +3 -3
  32. package/lib/relay-hooks/useRelayLoggingContext.js +9 -0
  33. package/package.json +3 -3
  34. package/relay-hooks/EntryPointContainer.react.js.flow +13 -17
  35. package/relay-hooks/EntryPointTypes.flow.js.flow +7 -4
  36. package/relay-hooks/MatchContainer.js.flow +1 -1
  37. package/relay-hooks/legacy/FragmentResource.js.flow +3 -6
  38. package/relay-hooks/legacy/useBlockingPaginationFragment.js.flow +2 -17
  39. package/relay-hooks/legacy/useRefetchableFragmentNode.js.flow +1 -1
  40. package/relay-hooks/loadEntryPoint.js.flow +6 -0
  41. package/relay-hooks/loadQuery.js.flow +20 -4
  42. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +18 -3
  43. package/relay-hooks/readFragmentInternal.js.flow +6 -6
  44. package/relay-hooks/useEntryPointLoader.js.flow +5 -3
  45. package/relay-hooks/useFragment.js.flow +1 -0
  46. package/relay-hooks/useFragmentInternal.js.flow +2 -0
  47. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +26 -6
  48. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +53 -14
  49. package/relay-hooks/useLazyLoadQuery.js.flow +62 -13
  50. package/relay-hooks/useLazyLoadQueryNode.js.flow +1 -1
  51. package/relay-hooks/useLoadMoreFunction.js.flow +7 -7
  52. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +5 -7
  53. package/relay-hooks/usePaginationFragment.js.flow +6 -10
  54. package/relay-hooks/usePrefetchableForwardPaginationFragment.js.flow +436 -0
  55. package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +15 -13
  56. package/relay-hooks/usePreloadedQuery.js.flow +2 -1
  57. package/relay-hooks/useQueryLoader.js.flow +6 -2
  58. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +3 -1
  59. package/relay-hooks/useRefetchableFragment.js.flow +1 -0
  60. package/relay-hooks/useRefetchableFragmentInternal.js.flow +3 -3
  61. package/relay-hooks/useRelayEnvironment.js.flow +22 -0
  62. package/relay-hooks/useRelayLoggingContext.js.flow +21 -0
  63. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +1 -0
  64. package/react-relay-hooks.js +0 -4
  65. package/react-relay-hooks.min.js +0 -9
  66. package/react-relay-legacy.js +0 -4
  67. package/react-relay-legacy.min.js +0 -9
  68. package/react-relay.js +0 -4
  69. package/react-relay.min.js +0 -9
@@ -62,6 +62,12 @@ function loadEntryPoint<
62
62
  const {environmentProviderOptions, options, parameters, variables} =
63
63
  query;
64
64
 
65
+ // $FlowFixMe[prop-missing] Exists for types that wrap EntryPoint
66
+ if (options?.includeIf === false) {
67
+ // don't preload this query since the includeIf is false
68
+ return;
69
+ }
70
+
65
71
  const environment = environmentProvider.getEnvironment(
66
72
  environmentProviderOptions,
67
73
  );
@@ -28,6 +28,7 @@ import type {
28
28
  RequestIdentifier,
29
29
  RequestParameters,
30
30
  } from 'relay-runtime';
31
+ import type {OperationAvailability} from 'relay-runtime/store/RelayStoreTypes';
31
32
 
32
33
  const invariant = require('invariant');
33
34
  const {
@@ -42,7 +43,7 @@ const {
42
43
 
43
44
  let fetchKey = 100001;
44
45
 
45
- type QueryType<T> =
46
+ export type QueryType<T> =
46
47
  T extends Query<infer V, infer D, infer RR>
47
48
  ? {
48
49
  variables: V,
@@ -125,11 +126,12 @@ function loadQuery<
125
126
  let networkError = null;
126
127
  // makeNetworkRequest will immediately start a raw network request if
127
128
  // one isn't already in flight and return an Observable that when
128
- // subscribed to will replay the network events that have occured so far,
129
+ // subscribed to will replay the network events that have occurred so far,
129
130
  // as well as subsequent events.
130
131
  let didMakeNetworkRequest = false;
131
132
  const makeNetworkRequest = (
132
133
  params: RequestParameters,
134
+ checkOperation?: () => OperationAvailability,
133
135
  ): Observable<GraphQLResponse> => {
134
136
  // N.B. this function is called synchronously or not at all
135
137
  // didMakeNetworkRequest is safe to rely on in the returned value
@@ -160,7 +162,16 @@ function loadQuery<
160
162
  'raw-network-request-' + getRequestIdentifier(params, variables);
161
163
  const observable = fetchQueryDeduped(environment, identifier, () => {
162
164
  const network = environment.getNetwork();
163
- return network.execute(params, variables, networkCacheConfig);
165
+ return network.execute(
166
+ params,
167
+ variables,
168
+ networkCacheConfig,
169
+ undefined,
170
+ undefined,
171
+ undefined,
172
+ undefined,
173
+ checkOperation,
174
+ );
164
175
  });
165
176
 
166
177
  const {unsubscribe} = observable.subscribe({
@@ -247,13 +258,18 @@ function loadQuery<
247
258
  // then we do nothing.
248
259
  const shouldFetch =
249
260
  fetchPolicy !== 'store-or-network' ||
261
+ // environment.check can trigger store updates through missing field handlers,
262
+ // short circuiting the check avoids unnecessary updates
250
263
  environment.check(operation).status !== 'available';
251
264
 
252
265
  if (shouldFetch) {
253
266
  executeDeduped(operation, () => {
254
267
  // N.B. Since we have the operation synchronously available here,
255
268
  // we can immediately fetch and execute the operation.
256
- const networkObservable = makeNetworkRequest(concreteRequest.params);
269
+ const networkObservable = makeNetworkRequest(
270
+ concreteRequest.params,
271
+ () => environment.check(operation),
272
+ );
257
273
  const executeObservable = executeWithNetworkSource(
258
274
  operation,
259
275
  networkObservable,
@@ -24,6 +24,7 @@ import type {
24
24
  GraphQLResponse,
25
25
  GraphQLTaggedNode,
26
26
  IEnvironment,
27
+ OperationAvailability,
27
28
  OperationType,
28
29
  Subscription,
29
30
  } from 'relay-runtime';
@@ -167,12 +168,17 @@ function preloadQueryDeduped<TQuery: OperationType>(
167
168
  }`;
168
169
  const prevQueryEntry = pendingQueries.get(cacheKey);
169
170
 
170
- const availability =
171
- fetchPolicy === STORE_OR_NETWORK_DEFAULT && query != null && query != null
171
+ function checkOperation(): OperationAvailability {
172
+ return query != null
172
173
  ? environment.check(
173
174
  createOperationDescriptor(query, variables, networkCacheConfig),
174
175
  )
175
176
  : {status: 'missing'};
177
+ }
178
+ const availability =
179
+ fetchPolicy === STORE_OR_NETWORK_DEFAULT
180
+ ? checkOperation()
181
+ : {status: 'missing'};
176
182
 
177
183
  let nextQueryEntry: ?PendingQueryEntry;
178
184
  if (availability.status === 'available' && query != null) {
@@ -203,7 +209,16 @@ function preloadQueryDeduped<TQuery: OperationType>(
203
209
  }
204
210
  } else if (prevQueryEntry == null || prevQueryEntry.kind !== 'network') {
205
211
  // Should fetch but we're not already fetching: fetch!
206
- const source = network.execute(params, variables, networkCacheConfig, null);
212
+ const source = network.execute(
213
+ params,
214
+ variables,
215
+ networkCacheConfig,
216
+ null,
217
+ undefined,
218
+ undefined,
219
+ undefined,
220
+ checkOperation,
221
+ );
207
222
  const subject = new ReplaySubject<GraphQLResponse>();
208
223
  nextQueryEntry = {
209
224
  cacheKey,
@@ -83,13 +83,10 @@ function handlePotentialSnapshotErrorsForState(
83
83
  state: FragmentState,
84
84
  ): void {
85
85
  if (state.kind === 'singular') {
86
- handlePotentialSnapshotErrors(
87
- environment,
88
- state.snapshot.errorResponseFields,
89
- );
86
+ handlePotentialSnapshotErrors(environment, state.snapshot.fieldErrors);
90
87
  } else if (state.kind === 'plural') {
91
88
  for (const snapshot of state.snapshots) {
92
- handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
89
+ handlePotentialSnapshotErrors(environment, snapshot.fieldErrors);
93
90
  }
94
91
  }
95
92
  }
@@ -216,7 +213,10 @@ function readFragmentInternal(
216
213
  // Handle the queries for any missing client edges; this may suspend.
217
214
  // FIXME handle client edges in parallel.
218
215
  let clientEdgeQueries = null;
219
- if (fragmentNode.metadata?.hasClientEdges === true) {
216
+ if (
217
+ fragmentNode.metadata?.hasClientEdges === true ||
218
+ RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
219
+ ) {
220
220
  const missingClientEdges = getMissingClientEdges(state);
221
221
  if (missingClientEdges?.length) {
222
222
  clientEdgeQueries = ([]: Array<QueryResult>);
@@ -48,7 +48,9 @@ type UseEntryPointLoaderHookReturnType<
48
48
  type NullEntryPointReference = {
49
49
  kind: 'NullEntryPointReference',
50
50
  };
51
- const initialNullEntryPointReferenceState = {kind: 'NullEntryPointReference'};
51
+ const initialNullEntryPointReferenceState: NullEntryPointReference = {
52
+ kind: 'NullEntryPointReference',
53
+ };
52
54
 
53
55
  hook useLoadEntryPoint<
54
56
  TEntryPointParams: {...},
@@ -122,7 +124,7 @@ hook useLoadEntryPoint<
122
124
 
123
125
  const disposeEntryPoint = useCallback(() => {
124
126
  if (isMountedRef.current) {
125
- const nullEntryPointReference = {
127
+ const nullEntryPointReference: NullEntryPointReference = {
126
128
  kind: 'NullEntryPointReference',
127
129
  };
128
130
  undisposedEntryPointReferencesRef.current.add(nullEntryPointReference);
@@ -156,7 +158,7 @@ hook useLoadEntryPoint<
156
158
  useEffect(() => {
157
159
  return () => {
158
160
  // Attempt to detect if the component was
159
- // hidden (by Offscreen API), or fast refresh occured;
161
+ // hidden (by Offscreen API), or fast refresh occurred;
160
162
  // Only in these situations would the effect cleanup
161
163
  // for "unmounting" run multiple times, so if
162
164
  // we are ever able to read this ref with a value
@@ -54,6 +54,7 @@ hook useFragment(fragment: GraphQLTaggedNode, key: mixed): mixed {
54
54
  if (__DEV__) {
55
55
  // eslint-disable-next-line react-hooks/rules-of-hooks
56
56
  // $FlowFixMe[react-rule-hook]
57
+ // $FlowFixMe[react-rule-hook-conditional]
57
58
  useDebugValue({fragment: fragmentNode.name, data});
58
59
  }
59
60
  return data;
@@ -26,6 +26,7 @@ hook useFragmentInternal(
26
26
  ): ?SelectorData | Array<?SelectorData> {
27
27
  if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
28
28
  // $FlowFixMe[react-rule-hook] - the condition is static
29
+ // $FlowFixMe[react-rule-hook-conditional]
29
30
  return useFragmentInternal_EXPERIMENTAL(
30
31
  fragmentNode,
31
32
  fragmentRef,
@@ -34,6 +35,7 @@ hook useFragmentInternal(
34
35
  );
35
36
  }
36
37
  // $FlowFixMe[react-rule-hook] - the condition is static
38
+ // $FlowFixMe[react-rule-hook-conditional]
37
39
  return useFragmentInternal_CURRENT(
38
40
  fragmentNode,
39
41
  fragmentRef,
@@ -26,6 +26,7 @@ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreT
26
26
 
27
27
  const {getQueryResourceForEnvironment} = require('./QueryResource');
28
28
  const useRelayEnvironment = require('./useRelayEnvironment');
29
+ const useRelayLoggingContext = require('./useRelayLoggingContext');
29
30
  const invariant = require('invariant');
30
31
  const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
31
32
  const {
@@ -109,15 +110,21 @@ function getSuspendingLiveResolver(
109
110
  function handlePotentialSnapshotErrorsForState(
110
111
  environment: IEnvironment,
111
112
  state: FragmentState,
113
+ loggingContext: mixed | void,
112
114
  ): void {
113
115
  if (state.kind === 'singular') {
114
116
  handlePotentialSnapshotErrors(
115
117
  environment,
116
- state.snapshot.errorResponseFields,
118
+ state.snapshot.fieldErrors,
119
+ loggingContext,
117
120
  );
118
121
  } else if (state.kind === 'plural') {
119
122
  for (const snapshot of state.snapshots) {
120
- handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
123
+ handlePotentialSnapshotErrors(
124
+ environment,
125
+ snapshot.fieldErrors,
126
+ loggingContext,
127
+ );
121
128
  }
122
129
  }
123
130
  }
@@ -153,7 +160,7 @@ function handleMissedUpdates(
153
160
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
154
161
  seenRecords: currentSnapshot.seenRecords,
155
162
  selector: currentSnapshot.selector,
156
- errorResponseFields: currentSnapshot.errorResponseFields,
163
+ fieldErrors: currentSnapshot.fieldErrors,
157
164
  };
158
165
  return [
159
166
  updatedData !== state.snapshot.data,
@@ -177,7 +184,7 @@ function handleMissedUpdates(
177
184
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
178
185
  seenRecords: currentSnapshot.seenRecords,
179
186
  selector: currentSnapshot.selector,
180
- errorResponseFields: currentSnapshot.errorResponseFields,
187
+ fieldErrors: currentSnapshot.fieldErrors,
181
188
  };
182
189
  if (updatedData !== snapshot.data) {
183
190
  didMissUpdates = true;
@@ -403,6 +410,12 @@ hook useFragmentInternal(
403
410
  );
404
411
 
405
412
  const environment = useRelayEnvironment();
413
+ let loggerContext;
414
+ if (RelayFeatureFlags.ENABLE_UI_CONTEXT_ON_RELAY_LOGGER) {
415
+ // $FlowFixMe[react-rule-hook] - the condition is static
416
+ // $FlowFixMe[react-rule-hook-conditional]
417
+ loggerContext = useRelayLoggingContext();
418
+ }
406
419
  const [_state, setState] = useState<FragmentState>(() =>
407
420
  getFragmentState(environment, fragmentSelector),
408
421
  );
@@ -448,12 +461,16 @@ hook useFragmentInternal(
448
461
 
449
462
  // Handle the queries for any missing client edges; this may suspend.
450
463
  // FIXME handle client edges in parallel.
451
- if (fragmentNode.metadata?.hasClientEdges === true) {
464
+ if (
465
+ fragmentNode.metadata?.hasClientEdges === true ||
466
+ RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
467
+ ) {
452
468
  // The fragment is validated to be static (in useFragment) and hasClientEdges is
453
469
  // a static (constant) property of the fragment. In practice, this effect will
454
470
  // always or never run for a given invocation of this hook.
455
471
  // eslint-disable-next-line react-hooks/rules-of-hooks
456
472
  // $FlowFixMe[react-rule-hook]
473
+ // $FlowFixMe[react-rule-hook-conditional]
457
474
  const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
458
475
  const missingClientEdges = getMissingClientEdges(state);
459
476
  // eslint-disable-next-line no-shadow
@@ -485,6 +502,7 @@ hook useFragmentInternal(
485
502
  // See above note
486
503
  // eslint-disable-next-line react-hooks/rules-of-hooks
487
504
  // $FlowFixMe[react-rule-hook]
505
+ // $FlowFixMe[react-rule-hook-conditional]
488
506
  useEffect(() => {
489
507
  const QueryResource = getQueryResourceForEnvironment(environment);
490
508
  if (clientEdgeQueries?.length) {
@@ -541,7 +559,7 @@ hook useFragmentInternal(
541
559
 
542
560
  // Report required fields only if we're not suspending, since that means
543
561
  // they're missing even though we are out of options for possibly fetching them:
544
- handlePotentialSnapshotErrorsForState(environment, state);
562
+ handlePotentialSnapshotErrorsForState(environment, state, loggerContext);
545
563
 
546
564
  const hasPendingStateChanges = useRef<boolean>(false);
547
565
 
@@ -594,6 +612,7 @@ hook useFragmentInternal(
594
612
  const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
595
613
  // eslint-disable-next-line react-hooks/rules-of-hooks
596
614
  // $FlowFixMe[react-rule-hook]
615
+ // $FlowFixMe[react-rule-hook-conditional]
597
616
  data = useMemo(() => {
598
617
  if (state.kind === 'bailout') {
599
618
  // Bailout state can happen if the fragmentRef is a plural array that is empty or has no
@@ -645,6 +664,7 @@ hook useFragmentInternal(
645
664
  if (__DEV__) {
646
665
  // eslint-disable-next-line react-hooks/rules-of-hooks
647
666
  // $FlowFixMe[react-rule-hook]
667
+ // $FlowFixMe[react-rule-hook-conditional]
648
668
  useDebugValue({fragment: fragmentNode.name, data});
649
669
  }
650
670
 
@@ -26,6 +26,7 @@ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreT
26
26
 
27
27
  const {getQueryResourceForEnvironment} = require('./QueryResource');
28
28
  const useRelayEnvironment = require('./useRelayEnvironment');
29
+ const useRelayLoggingContext = require('./useRelayLoggingContext');
29
30
  const invariant = require('invariant');
30
31
  const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
31
32
  const {
@@ -121,15 +122,21 @@ function getSuspendingLiveResolver(
121
122
  function handlePotentialSnapshotErrorsForState(
122
123
  environment: IEnvironment,
123
124
  state: FragmentState,
125
+ loggingContext: mixed | void,
124
126
  ): void {
125
127
  if (state.kind === 'singular') {
126
128
  handlePotentialSnapshotErrors(
127
129
  environment,
128
- state.snapshot.errorResponseFields,
130
+ state.snapshot.fieldErrors,
131
+ loggingContext,
129
132
  );
130
133
  } else if (state.kind === 'plural') {
131
134
  for (const snapshot of state.snapshots) {
132
- handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
135
+ handlePotentialSnapshotErrors(
136
+ environment,
137
+ snapshot.fieldErrors,
138
+ loggingContext,
139
+ );
133
140
  }
134
141
  }
135
142
  }
@@ -165,7 +172,7 @@ function handleMissedUpdates(
165
172
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
166
173
  seenRecords: currentSnapshot.seenRecords,
167
174
  selector: currentSnapshot.selector,
168
- errorResponseFields: currentSnapshot.errorResponseFields,
175
+ fieldErrors: currentSnapshot.fieldErrors,
169
176
  };
170
177
  return [
171
178
  updatedData !== state.snapshot.data,
@@ -191,7 +198,7 @@ function handleMissedUpdates(
191
198
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
192
199
  seenRecords: currentSnapshot.seenRecords,
193
200
  selector: currentSnapshot.selector,
194
- errorResponseFields: currentSnapshot.errorResponseFields,
201
+ fieldErrors: currentSnapshot.fieldErrors,
195
202
  };
196
203
  if (updatedData !== snapshot.data) {
197
204
  didMissUpdates = true;
@@ -215,13 +222,15 @@ function handleMissedUpdates(
215
222
  }
216
223
  }
217
224
 
225
+ type PromiseWithDisplayName = Promise<mixed> & {displayName?: string};
226
+
218
227
  function handleMissingClientEdge(
219
228
  environment: IEnvironment,
220
229
  parentFragmentNode: ReaderFragment,
221
230
  parentFragmentRef: mixed,
222
231
  missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
223
232
  queryOptions?: FragmentQueryOptions,
224
- ): [QueryResult, ?Promise<mixed>] {
233
+ ): [QueryResult, ?PromiseWithDisplayName] {
225
234
  const originalVariables = getVariablesFromFragment(
226
235
  parentFragmentNode,
227
236
  parentFragmentRef,
@@ -246,10 +255,17 @@ function handleMissingClientEdge(
246
255
  queryOptions?.fetchPolicy,
247
256
  );
248
257
 
249
- return [
250
- queryResult,
251
- getPromiseForActiveRequest(environment, queryOperationDescriptor.request),
252
- ];
258
+ const promise = getPromiseForActiveRequest(
259
+ environment,
260
+ queryOperationDescriptor.request,
261
+ );
262
+ // $FlowExpectedError[prop-missing]
263
+ if (promise != null && promise.displayName == null) {
264
+ // $FlowExpectedError[prop-missing]
265
+ promise.displayName = missingClientEdgeRequestInfo.request.params.name;
266
+ }
267
+ // $FlowFixMe[incompatible-exact] - Intentionally bypassing exactness check
268
+ return [queryResult, promise];
253
269
  }
254
270
 
255
271
  function subscribeToSnapshot(
@@ -431,6 +447,12 @@ hook useFragmentInternal_EXPERIMENTAL(
431
447
  );
432
448
 
433
449
  const environment = useRelayEnvironment();
450
+ let loggerContext;
451
+ if (RelayFeatureFlags.ENABLE_UI_CONTEXT_ON_RELAY_LOGGER) {
452
+ // $FlowFixMe[react-rule-hook] - the condition is static
453
+ // $FlowFixMe[react-rule-hook-conditional]
454
+ loggerContext = useRelayLoggingContext();
455
+ }
434
456
  const [_state, setState] = useState<FragmentState>(() =>
435
457
  getFragmentState(environment, fragmentSelector),
436
458
  );
@@ -463,17 +485,21 @@ hook useFragmentInternal_EXPERIMENTAL(
463
485
 
464
486
  // Handle the queries for any missing client edges; this may suspend.
465
487
  // FIXME handle client edges in parallel.
466
- if (fragmentNode.metadata?.hasClientEdges === true) {
488
+ if (
489
+ fragmentNode.metadata?.hasClientEdges === true ||
490
+ RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
491
+ ) {
467
492
  // The fragment is validated to be static (in useFragment) and hasClientEdges is
468
493
  // a static (constant) property of the fragment. In practice, this effect will
469
494
  // always or never run for a given invocation of this hook.
470
495
  // eslint-disable-next-line react-hooks/rules-of-hooks
471
496
  // $FlowFixMe[react-rule-hook]
497
+ // $FlowFixMe[react-rule-hook-conditional]
472
498
  const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
473
499
  const missingClientEdges = getMissingClientEdges(state);
474
500
  // eslint-disable-next-line no-shadow
475
501
  let clientEdgeQueries;
476
- const activeRequestPromises = [];
502
+ const activeRequestPromises: Array<PromiseWithDisplayName> = [];
477
503
  if (missingClientEdges?.length) {
478
504
  clientEdgeQueries = ([]: Array<QueryResult>);
479
505
  for (const edge of missingClientEdges) {
@@ -494,12 +520,18 @@ hook useFragmentInternal_EXPERIMENTAL(
494
520
  }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
495
521
 
496
522
  if (activeRequestPromises.length) {
497
- throw Promise.all(activeRequestPromises);
523
+ const allPromises = Promise.all(activeRequestPromises);
524
+ // $FlowExpectedError[prop-missing] Expando to annotate Promises.
525
+ allPromises.displayName = `RelayClientEdge(${activeRequestPromises
526
+ .map(promise => promise.displayName)
527
+ .join(',')})`;
528
+ throw allPromises;
498
529
  }
499
530
 
500
531
  // See above note
501
532
  // eslint-disable-next-line react-hooks/rules-of-hooks
502
533
  // $FlowFixMe[react-rule-hook]
534
+ // $FlowFixMe[react-rule-hook-conditional]
503
535
  useEffect(() => {
504
536
  const QueryResource = getQueryResourceForEnvironment(environment);
505
537
  if (clientEdgeQueries?.length) {
@@ -520,12 +552,15 @@ hook useFragmentInternal_EXPERIMENTAL(
520
552
  // Suspend if a Live Resolver within this fragment is in a suspended state:
521
553
  const suspendingLiveResolvers = getSuspendingLiveResolver(state);
522
554
  if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
523
- throw Promise.all(
555
+ const promise = Promise.all(
524
556
  suspendingLiveResolvers.map(liveStateID => {
525
557
  // $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
526
558
  return environment.getStore().getLiveResolverPromise(liveStateID);
527
559
  }),
528
560
  );
561
+ // $FlowExpectedError[prop-missing] Expando to annotate Promises.
562
+ promise.displayName = 'RelayLiveResolver(' + fragmentNode.name + ')';
563
+ throw promise;
529
564
  }
530
565
  // Suspend if an active operation bears on this fragment, either the
531
566
  // fragment's owner or some other mutation etc. that could affect it.
@@ -556,7 +591,7 @@ hook useFragmentInternal_EXPERIMENTAL(
556
591
 
557
592
  // Report required fields only if we're not suspending, since that means
558
593
  // they're missing even though we are out of options for possibly fetching them:
559
- handlePotentialSnapshotErrorsForState(environment, state);
594
+ handlePotentialSnapshotErrorsForState(environment, state, loggerContext);
560
595
 
561
596
  // We emulate CRUD effects using a ref and two effects:
562
597
  // - The ref tracks the current state (including updates from the subscription)
@@ -577,6 +612,7 @@ hook useFragmentInternal_EXPERIMENTAL(
577
612
  selector: ?ReaderSelector,
578
613
  environment: IEnvironment,
579
614
  } | null>(null);
615
+ // $FlowFixMe[react-rule-hook] - the condition is static
580
616
  useEffect(() => {
581
617
  const storeSubscription = storeSubscriptionRef.current;
582
618
  if (storeSubscription != null) {
@@ -629,6 +665,7 @@ hook useFragmentInternal_EXPERIMENTAL(
629
665
  environment: state.environment,
630
666
  };
631
667
  }, [state]);
668
+ // $FlowFixMe[react-rule-hook] - the condition is static
632
669
  useEffect(() => {
633
670
  if (storeSubscriptionRef.current == null && state.kind !== 'bailout') {
634
671
  const dispose = subscribeToSnapshot(state.environment, state, setState);
@@ -656,6 +693,7 @@ hook useFragmentInternal_EXPERIMENTAL(
656
693
  const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
657
694
  // eslint-disable-next-line react-hooks/rules-of-hooks
658
695
  // $FlowFixMe[react-rule-hook]
696
+ // $FlowFixMe[react-rule-hook-conditional]
659
697
  data = useMemo(() => {
660
698
  if (state.kind === 'bailout') {
661
699
  // Bailout state can happen if the fragmentRef is a plural array that is empty or has no
@@ -707,6 +745,7 @@ hook useFragmentInternal_EXPERIMENTAL(
707
745
  if (__DEV__) {
708
746
  // eslint-disable-next-line react-hooks/rules-of-hooks
709
747
  // $FlowFixMe[react-rule-hook]
748
+ // $FlowFixMe[react-rule-hook-conditional]
710
749
  useDebugValue({fragment: fragmentNode.name, data});
711
750
  }
712
751
 
@@ -32,23 +32,72 @@ const {
32
32
  export type UseLazyLoadQueryHookType = hook <TVariables: Variables, TData>(
33
33
  gqlQuery: Query<TVariables, TData>,
34
34
  variables: TVariables,
35
- options?: {
36
- fetchKey?: string | number,
37
- fetchPolicy?: FetchPolicy,
38
- networkCacheConfig?: CacheConfig,
39
- UNSTABLE_renderPolicy?: RenderPolicy,
40
- },
35
+ options?: Options,
41
36
  ) => TData;
42
37
 
38
+ type Options = {
39
+ /**
40
+ * Determines if cached data should be used, and when to send a network request based on the cached data that is currently available in the Relay store (for more details, see our [Fetch Policies](../../guided-tour/reusing-cached-data/fetch-policies) and [Garbage Collection](../../guided-tour/reusing-cached-data/presence-of-data) guides):
41
+ * * "store-or-network": _*(default)*_ *will* reuse locally cached data and will *only* send a network request if any data for the query is missing. If the query is fully cached, a network request will *not* be made.
42
+ * * "store-and-network": *will* reuse locally cached data and will *always* send a network request, regardless of whether any data was missing from the local cache or not.
43
+ * * "network-only": *will* *not* reuse locally cached data, and will *always* send a network request to fetch the query, ignoring any data that might be locally cached in Relay.
44
+ * * "store-only": *will* *only* reuse locally cached data, and will *never* send a network request to fetch the query. In this case, the responsibility of fetching the query falls to the caller, but this policy could also be used to read and operate on data that is entirely [local](../../guided-tour/updating-data/local-data-updates).
45
+ */
46
+ +fetchPolicy?: FetchPolicy,
47
+ /**
48
+ * A `fetchKey` can be passed to force a re-evaluation of the current query and variables when the component re-renders, even if the variables didn't change, or even if the component isn't remounted (similarly to how passing a different `key` to a React component will cause it to remount). If the `fetchKey` is different from the one used in the previous render, the current query will be re-evaluated against the store, and it might be refetched depending on the current `fetchPolicy` and the state of the cache.
49
+ */
50
+ +fetchKey?: string | number,
51
+ /**
52
+ * Default value: `{force: true}`. Object containing cache config options for the *network layer*. Note that the network layer may contain an *additional* query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely (which is the default behavior), pass `{force: true}` as the value for this option.
53
+ */
54
+ +networkCacheConfig?: CacheConfig,
55
+ /**
56
+ * Undocumented option.
57
+ */
58
+ +UNSTABLE_renderPolicy?: RenderPolicy,
59
+ };
60
+
61
+ /**
62
+ * Hook used to fetch a GraphQL query during render. This hook can trigger multiple nested or waterfalling round trips if used without caution, and waits until render to start a data fetch (when it can usually start a lot sooner than render), thereby degrading performance. Instead, prefer [`usePreloadedQuery`](../use-preloaded-query).
63
+ *
64
+ * @example
65
+ * const React = require('React');
66
+ *
67
+ * const {graphql, useLazyLoadQuery} = require('react-relay');
68
+ *
69
+ * function App() {
70
+ * const data = useLazyLoadQuery(
71
+ * graphql`
72
+ * query AppQuery($id: ID!) {
73
+ * user(id: $id) {
74
+ * name
75
+ * }
76
+ * }
77
+ * `,
78
+ * {id: 4},
79
+ * {fetchPolicy: 'store-or-network'},
80
+ * );
81
+ *
82
+ * return <h1>{data.user?.name}</h1>;
83
+ * }
84
+ *
85
+ * @returns - `data`: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
86
+ * - The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of `data` above is: `{| user: ?{| name: ?string |} |}`.
87
+ */
43
88
  hook useLazyLoadQuery<TVariables: Variables, TData>(
89
+ /**
90
+ * GraphQL query specified using a `graphql` template literal.
91
+ */
44
92
  gqlQuery: Query<TVariables, TData>,
93
+ /**
94
+ * Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.
95
+ */
45
96
  variables: TVariables,
46
- options?: {
47
- fetchKey?: string | number,
48
- fetchPolicy?: FetchPolicy,
49
- networkCacheConfig?: CacheConfig,
50
- UNSTABLE_renderPolicy?: RenderPolicy,
51
- },
97
+ /**
98
+ * options object
99
+ */
100
+ options?: Options,
52
101
  ): TData {
53
102
  const environment = useRelayEnvironment();
54
103
 
@@ -71,4 +120,4 @@ hook useLazyLoadQuery<TVariables: Variables, TData>(
71
120
  }
72
121
 
73
122
  // $FlowFixMe[react-rule-hook-incompatible]
74
- module.exports = (useLazyLoadQuery: UseLazyLoadQueryHookType);
123
+ module.exports = useLazyLoadQuery;
@@ -78,7 +78,7 @@ hook useLazyLoadQueryNode<TQuery: OperationType>({
78
78
  useEffect(() => {
79
79
  return () => {
80
80
  // Attempt to detect if the component was
81
- // hidden (by Offscreen API), or fast refresh occured;
81
+ // hidden (by Offscreen API), or fast refresh occurred;
82
82
  // Only in these situations would the effect cleanup
83
83
  // for "unmounting" run multiple times, so if
84
84
  // we are ever able to read this ref with a value
@@ -67,9 +67,11 @@ hook useLoadMoreFunction<TVariables: Variables>(
67
67
  ): [LoadMoreFn<TVariables>, boolean, () => void] {
68
68
  if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
69
69
  // $FlowFixMe[react-rule-hook] - the condition is static
70
+ // $FlowFixMe[react-rule-hook-conditional]
70
71
  return useLoadMoreFunction_EXPERIMENTAL(args);
71
72
  }
72
73
  // $FlowFixMe[react-rule-hook] - the condition is static
74
+ // $FlowFixMe[react-rule-hook-conditional]
73
75
  return useLoadMoreFunction_CURRENT(args);
74
76
  }
75
77
 
@@ -138,6 +140,8 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
138
140
  };
139
141
  }, [disposeFetch]);
140
142
 
143
+ const isRequestInvalid = fragmentData == null || isParentQueryActive;
144
+
141
145
  const loadMore = useCallback(
142
146
  (
143
147
  count: number,
@@ -166,11 +170,8 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
166
170
  }
167
171
 
168
172
  const fragmentSelector = getSelector(fragmentNode, fragmentRef);
169
- if (
170
- isFetchingRef.current === true ||
171
- fragmentData == null ||
172
- isParentQueryActive
173
- ) {
173
+
174
+ if (isFetchingRef.current === true || isRequestInvalid) {
174
175
  if (fragmentSelector == null) {
175
176
  warning(
176
177
  false,
@@ -271,8 +272,7 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
271
272
  disposeFetch,
272
273
  completeFetch,
273
274
  isFetchingRef,
274
- isParentQueryActive,
275
- fragmentData,
275
+ isRequestInvalid,
276
276
  fragmentNode.name,
277
277
  fragmentRef,
278
278
  componentDisplayName,