react-relay 20.1.0 → 21.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 (129) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayContext.js.flow +2 -2
  3. package/ReactRelayFragmentContainer.js.flow +9 -10
  4. package/ReactRelayLocalQueryRenderer.js.flow +11 -3
  5. package/ReactRelayLoggingContext.js.flow +3 -3
  6. package/ReactRelayPaginationContainer.js.flow +32 -25
  7. package/ReactRelayQueryFetcher.js.flow +1 -1
  8. package/ReactRelayQueryRenderer.js.flow +2 -2
  9. package/ReactRelayQueryRendererContext.js.flow +2 -2
  10. package/ReactRelayRefetchContainer.js.flow +18 -15
  11. package/ReactRelayTestMocker.js.flow +10 -10
  12. package/ReactRelayTypes.js.flow +18 -20
  13. package/RelayContext.js.flow +3 -3
  14. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +11 -11
  15. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +5 -5
  16. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +5 -5
  17. package/__flowtests__/RelayModern-flowtest.js.flow +24 -27
  18. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +1 -1
  19. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +3 -4
  20. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +3 -4
  21. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +9 -10
  22. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +4 -5
  23. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +9 -10
  24. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +4 -5
  25. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +3 -4
  26. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +3 -4
  27. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +3 -4
  28. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +5 -6
  29. package/buildReactRelayContainer.js.flow +4 -4
  30. package/getRootVariablesForFragments.js.flow +3 -3
  31. package/hooks.js +1 -1
  32. package/hooks.js.flow +23 -8
  33. package/index.js +1 -1
  34. package/index.js.flow +40 -14
  35. package/isRelayEnvironment.js.flow +1 -1
  36. package/jest-react/internalAct.js.flow +1 -1
  37. package/legacy.js +1 -1
  38. package/legacy.js.flow +32 -13
  39. package/lib/ReactRelayFragmentContainer.js +1 -1
  40. package/lib/ReactRelayPaginationContainer.js +8 -8
  41. package/lib/ReactRelayRefetchContainer.js +8 -8
  42. package/lib/ReactRelayTestMocker.js +5 -5
  43. package/lib/hooks.js +18 -8
  44. package/lib/index.js +30 -14
  45. package/lib/legacy.js +26 -13
  46. package/lib/relay-hooks/legacy/useBlockingPaginationFragment.js +5 -5
  47. package/lib/relay-hooks/legacy/useRefetchableFragmentNode.js +34 -34
  48. package/lib/relay-hooks/loadEntryPoint.js +2 -2
  49. package/lib/relay-hooks/loadQuery.js +14 -14
  50. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +10 -10
  51. package/lib/relay-hooks/readFragmentInternal.js +6 -6
  52. package/lib/relay-hooks/rsc/serverFetchQuery.js +20 -0
  53. package/lib/relay-hooks/rsc/serverPreloadQuery.js +31 -0
  54. package/lib/relay-hooks/rsc/serverReadFragment.js +15 -0
  55. package/lib/relay-hooks/rsc/useQueryFromServer.js +62 -0
  56. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +49 -25
  57. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +81 -44
  58. package/lib/relay-hooks/useLazyLoadQueryNode.js +32 -19
  59. package/lib/relay-hooks/useMutation.js +6 -14
  60. package/lib/relay-hooks/useMutationAction_EXPERIMENTAL.js +26 -0
  61. package/lib/relay-hooks/usePreloadedQuery.js +52 -47
  62. package/lib/relay-hooks/useQueryLoader.js +2 -2
  63. package/lib/relay-hooks/useQueryLoader_EXPERIMENTAL.js +2 -2
  64. package/lib/relay-hooks/useRefetchableFragmentInternal.js +31 -31
  65. package/lib/rsc-client_EXPERIMENTAL.js +7 -0
  66. package/lib/rsc_EXPERIMENTAL.js +43 -0
  67. package/multi-actor/ActorChange.js.flow +1 -1
  68. package/package.json +3 -2
  69. package/relay-hooks/EntryPointContainer.react.js.flow +6 -6
  70. package/relay-hooks/EntryPointTypes.flow.js.flow +61 -67
  71. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +23 -21
  72. package/relay-hooks/MatchContainer.js.flow +12 -6
  73. package/relay-hooks/NestedRelayEntryPointBuilderUtils.js.flow +3 -9
  74. package/relay-hooks/QueryResource.js.flow +6 -6
  75. package/relay-hooks/RelayEnvironmentProvider.js.flow +2 -2
  76. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +6 -6
  77. package/relay-hooks/__flowtests__/EntryPointTypes/ExtractQueryTypes-flowtest.js.flow +48 -1
  78. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +9 -9
  79. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +3 -4
  80. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +5 -6
  81. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +27 -32
  82. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +25 -25
  83. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +26 -32
  84. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +23 -30
  85. package/relay-hooks/__flowtests__/utils.js.flow +4 -4
  86. package/relay-hooks/getConnectionState.js.flow +4 -4
  87. package/relay-hooks/legacy/FragmentResource.js.flow +13 -13
  88. package/relay-hooks/legacy/useBlockingPaginationFragment.js.flow +24 -25
  89. package/relay-hooks/legacy/useFragmentNode.js.flow +4 -4
  90. package/relay-hooks/legacy/useRefetchableFragmentNode.js.flow +79 -81
  91. package/relay-hooks/loadEntryPoint.js.flow +16 -14
  92. package/relay-hooks/loadQuery.js.flow +18 -18
  93. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +16 -13
  94. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +7 -7
  95. package/relay-hooks/readFragmentInternal.js.flow +9 -9
  96. package/relay-hooks/rsc/serverFetchQuery.js.flow +31 -0
  97. package/relay-hooks/rsc/serverPreloadQuery.js.flow +69 -0
  98. package/relay-hooks/rsc/serverReadFragment.js.flow +33 -0
  99. package/relay-hooks/rsc/useQueryFromServer.js.flow +135 -0
  100. package/relay-hooks/useClientQuery.js.flow +2 -2
  101. package/relay-hooks/useEntryPointLoader.js.flow +11 -11
  102. package/relay-hooks/useFragment.js.flow +7 -7
  103. package/relay-hooks/useFragmentInternal.js.flow +1 -1
  104. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +54 -22
  105. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +95 -46
  106. package/relay-hooks/useIsOperationNodeActive.js.flow +1 -1
  107. package/relay-hooks/useIsParentQueryActive.js.flow +1 -1
  108. package/relay-hooks/useLazyLoadQuery.js.flow +10 -3
  109. package/relay-hooks/useLazyLoadQueryNode.js.flow +67 -28
  110. package/relay-hooks/useLoadMoreFunction.js.flow +7 -6
  111. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +5 -5
  112. package/relay-hooks/useMemoVariables.js.flow +1 -1
  113. package/relay-hooks/useMutation.js.flow +8 -16
  114. package/relay-hooks/useMutationAction_EXPERIMENTAL.js.flow +68 -0
  115. package/relay-hooks/usePaginationFragment.js.flow +15 -11
  116. package/relay-hooks/usePrefetchableForwardPaginationFragment.js.flow +19 -18
  117. package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +19 -18
  118. package/relay-hooks/usePreloadedQuery.js.flow +119 -85
  119. package/relay-hooks/useQueryLoader.js.flow +27 -23
  120. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +10 -10
  121. package/relay-hooks/useRefetchableFragment.js.flow +16 -11
  122. package/relay-hooks/useRefetchableFragmentInternal.js.flow +77 -79
  123. package/relay-hooks/useRelayLoggingContext.js.flow +1 -1
  124. package/relay-hooks/useSubscribeToInvalidationState.js.flow +1 -1
  125. package/relay-hooks/useSubscription.js.flow +1 -1
  126. package/rsc-client_EXPERIMENTAL.js +10 -0
  127. package/rsc-client_EXPERIMENTAL.js.flow +23 -0
  128. package/rsc_EXPERIMENTAL.js +10 -0
  129. package/rsc_EXPERIMENTAL.js.flow +90 -0
@@ -47,10 +47,10 @@ type FragmentQueryOptions = {
47
47
  networkCacheConfig?: ?CacheConfig,
48
48
  };
49
49
 
50
- type FragmentState = $ReadOnly<
50
+ type FragmentState = Readonly<
51
51
  | {kind: 'bailout'}
52
52
  | {kind: 'singular', snapshot: Snapshot, epoch: number}
53
- | {kind: 'plural', snapshots: $ReadOnlyArray<Snapshot>, epoch: number},
53
+ | {kind: 'plural', snapshots: ReadonlyArray<Snapshot>, epoch: number},
54
54
  >;
55
55
 
56
56
  type StateUpdaterFunction<T> = ((T) => T) => void;
@@ -67,7 +67,7 @@ function isMissingData(state: FragmentState): boolean {
67
67
 
68
68
  function getMissingClientEdges(
69
69
  state: FragmentState,
70
- ): $ReadOnlyArray<MissingClientEdgeRequestInfo> | null {
70
+ ): ReadonlyArray<MissingClientEdgeRequestInfo> | null {
71
71
  if (state.kind === 'bailout') {
72
72
  return null;
73
73
  } else if (state.kind === 'singular') {
@@ -88,7 +88,7 @@ function getMissingClientEdges(
88
88
 
89
89
  function getSuspendingLiveResolver(
90
90
  state: FragmentState,
91
- ): $ReadOnlyArray<DataID> | null {
91
+ ): ReadonlyArray<DataID> | null {
92
92
  if (state.kind === 'bailout') {
93
93
  return null;
94
94
  } else if (state.kind === 'singular') {
@@ -110,7 +110,7 @@ function getSuspendingLiveResolver(
110
110
  function handlePotentialSnapshotErrorsForState(
111
111
  environment: IEnvironment,
112
112
  state: FragmentState,
113
- loggingContext: mixed | void,
113
+ loggingContext: unknown | void,
114
114
  ): void {
115
115
  if (state.kind === 'singular') {
116
116
  handlePotentialSnapshotErrors(
@@ -155,19 +155,19 @@ function handleMissedUpdates(
155
155
  );
156
156
  const updatedCurrentSnapshot: Snapshot = {
157
157
  data: updatedData,
158
+ fieldErrors: currentSnapshot.fieldErrors,
158
159
  isMissingData: currentSnapshot.isMissingData,
159
160
  missingClientEdges: currentSnapshot.missingClientEdges,
160
161
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
161
162
  seenRecords: currentSnapshot.seenRecords,
162
163
  selector: currentSnapshot.selector,
163
- fieldErrors: currentSnapshot.fieldErrors,
164
164
  };
165
165
  return [
166
166
  updatedData !== state.snapshot.data,
167
167
  {
168
+ epoch: currentEpoch,
168
169
  kind: 'singular',
169
170
  snapshot: updatedCurrentSnapshot,
170
- epoch: currentEpoch,
171
171
  },
172
172
  ];
173
173
  } else {
@@ -179,12 +179,12 @@ function handleMissedUpdates(
179
179
  const updatedData = recycleNodesInto(snapshot.data, currentSnapshot.data);
180
180
  const updatedCurrentSnapshot: Snapshot = {
181
181
  data: updatedData,
182
+ fieldErrors: currentSnapshot.fieldErrors,
182
183
  isMissingData: currentSnapshot.isMissingData,
183
184
  missingClientEdges: currentSnapshot.missingClientEdges,
184
185
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
185
186
  seenRecords: currentSnapshot.seenRecords,
186
187
  selector: currentSnapshot.selector,
187
- fieldErrors: currentSnapshot.fieldErrors,
188
188
  };
189
189
  if (updatedData !== snapshot.data) {
190
190
  didMissUpdates = true;
@@ -198,9 +198,9 @@ function handleMissedUpdates(
198
198
  return [
199
199
  didMissUpdates,
200
200
  {
201
+ epoch: currentEpoch,
201
202
  kind: 'plural',
202
203
  snapshots: currentSnapshots,
203
- epoch: currentEpoch,
204
204
  },
205
205
  ];
206
206
  }
@@ -209,10 +209,10 @@ function handleMissedUpdates(
209
209
  function handleMissingClientEdge(
210
210
  environment: IEnvironment,
211
211
  parentFragmentNode: ReaderFragment,
212
- parentFragmentRef: mixed,
212
+ parentFragmentRef: unknown,
213
213
  missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
214
214
  queryOptions?: FragmentQueryOptions,
215
- ): [QueryResult, ?Promise<mixed>] {
215
+ ): [QueryResult, ?Promise<unknown>] {
216
216
  const originalVariables = getVariablesFromFragment(
217
217
  parentFragmentNode,
218
218
  parentFragmentRef,
@@ -265,8 +265,8 @@ function subscribeToSnapshot(
265
265
  if (updates != null) {
266
266
  const [dataChanged, nextState] = updates;
267
267
  environment.__log({
268
- name: 'useFragment.subscription.missedUpdates',
269
268
  hasDataChanges: dataChanged,
269
+ name: 'useFragment.subscription.missedUpdates',
270
270
  });
271
271
  hasPendingStateChanges.current = dataChanged;
272
272
  return dataChanged ? nextState : prevState;
@@ -277,9 +277,9 @@ function subscribeToSnapshot(
277
277
 
278
278
  hasPendingStateChanges.current = true;
279
279
  return {
280
+ epoch: environment.getStore().getEpoch(),
280
281
  kind: 'singular',
281
282
  snapshot: latestSnapshot,
282
- epoch: environment.getStore().getEpoch(),
283
283
  };
284
284
  });
285
285
  });
@@ -301,8 +301,8 @@ function subscribeToSnapshot(
301
301
  if (updates != null) {
302
302
  const [dataChanged, nextState] = updates;
303
303
  environment.__log({
304
- name: 'useFragment.subscription.missedUpdates',
305
304
  hasDataChanges: dataChanged,
305
+ name: 'useFragment.subscription.missedUpdates',
306
306
  });
307
307
  hasPendingStateChanges.current =
308
308
  hasPendingStateChanges.current || dataChanged;
@@ -315,9 +315,9 @@ function subscribeToSnapshot(
315
315
  updated[index] = latestSnapshot;
316
316
  hasPendingStateChanges.current = true;
317
317
  return {
318
+ epoch: environment.getStore().getEpoch(),
318
319
  kind: 'plural',
319
320
  snapshots: updated,
320
- epoch: environment.getStore().getEpoch(),
321
321
  };
322
322
  });
323
323
  }),
@@ -340,15 +340,15 @@ function getFragmentState(
340
340
  // Note that if fragmentRef is an empty array, fragmentSelector will be null so we'll hit the above case.
341
341
  // Null is returned by getSelector if fragmentRef has no non-null items.
342
342
  return {
343
+ epoch: environment.getStore().getEpoch(),
343
344
  kind: 'plural',
344
345
  snapshots: fragmentSelector.selectors.map(s => environment.lookup(s)),
345
- epoch: environment.getStore().getEpoch(),
346
346
  };
347
347
  } else {
348
348
  return {
349
+ epoch: environment.getStore().getEpoch(),
349
350
  kind: 'singular',
350
351
  snapshot: environment.lookup(fragmentSelector),
351
- epoch: environment.getStore().getEpoch(),
352
352
  };
353
353
  }
354
354
  }
@@ -356,7 +356,7 @@ function getFragmentState(
356
356
  // fragmentNode cannot change during the lifetime of the component, though fragmentRef may change.
357
357
  hook useFragmentInternal(
358
358
  fragmentNode: ReaderFragment,
359
- fragmentRef: mixed,
359
+ fragmentRef: unknown,
360
360
  hookDisplayName: string,
361
361
  queryOptions?: FragmentQueryOptions,
362
362
  ): ?SelectorData | Array<?SelectorData> {
@@ -478,7 +478,7 @@ hook useFragmentInternal(
478
478
  let clientEdgeQueries;
479
479
  const activeRequestPromises = [];
480
480
  if (missingClientEdges?.length) {
481
- clientEdgeQueries = ([]: Array<QueryResult>);
481
+ clientEdgeQueries = [] as Array<QueryResult>;
482
482
  for (const edge of missingClientEdges) {
483
483
  const [queryResult, requestPromise] = handleMissingClientEdge(
484
484
  environment,
@@ -497,6 +497,18 @@ hook useFragmentInternal(
497
497
  }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
498
498
 
499
499
  if (activeRequestPromises.length) {
500
+ invariant(fragmentSelector != null, 'refinement, see invariants above');
501
+ const fragmentOwner =
502
+ fragmentSelector.kind === 'PluralReaderSelector'
503
+ ? fragmentSelector.selectors[0].owner
504
+ : fragmentSelector.owner;
505
+ environment.__log({
506
+ name: 'suspense.client_edge',
507
+ fragment: fragmentNode,
508
+ fragmentOwner,
509
+ // $FlowFixMe[react-rule-unsafe-ref]
510
+ isMount: committedFragmentSelectorRef.current === false,
511
+ });
500
512
  throw Promise.all(activeRequestPromises);
501
513
  }
502
514
 
@@ -524,6 +536,19 @@ hook useFragmentInternal(
524
536
  // Suspend if a Live Resolver within this fragment is in a suspended state:
525
537
  const suspendingLiveResolvers = getSuspendingLiveResolver(state);
526
538
  if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
539
+ invariant(fragmentSelector != null, 'refinement, see invariants above');
540
+ const fragmentOwner =
541
+ fragmentSelector.kind === 'PluralReaderSelector'
542
+ ? fragmentSelector.selectors[0].owner
543
+ : fragmentSelector.owner;
544
+ environment.__log({
545
+ name: 'suspense.resolver',
546
+ fragment: fragmentNode,
547
+ fragmentOwner,
548
+ // $FlowFixMe[react-rule-unsafe-ref]
549
+ isMount: committedFragmentSelectorRef.current === false,
550
+ suspendingLiveResolvers,
551
+ });
527
552
  throw Promise.all(
528
553
  suspendingLiveResolvers.map(liveStateID => {
529
554
  // $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
@@ -538,8 +563,7 @@ hook useFragmentInternal(
538
563
  if (
539
564
  RelayFeatureFlags.ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE ||
540
565
  environment !== previousEnvironment ||
541
- // $FlowFixMe[sketchy-null-bool]
542
- !committedFragmentSelectorRef.current ||
566
+ committedFragmentSelectorRef.current === false ||
543
567
  // $FlowFixMe[react-rule-unsafe-ref]
544
568
  !areEqualSelectors(committedFragmentSelectorRef.current, fragmentSelector)
545
569
  ) {
@@ -554,6 +578,14 @@ hook useFragmentInternal(
554
578
  fragmentOwner,
555
579
  );
556
580
  if (pendingOperationsResult) {
581
+ environment.__log({
582
+ name: 'suspense.missing_data',
583
+ fragment: fragmentNode,
584
+ fragmentOwner,
585
+ // $FlowFixMe[react-rule-unsafe-ref]
586
+ isMount: committedFragmentSelectorRef.current === false,
587
+ pendingOperations: pendingOperationsResult.pendingOperations,
588
+ });
557
589
  throw pendingOperationsResult.promise;
558
590
  }
559
591
  }
@@ -667,7 +699,7 @@ hook useFragmentInternal(
667
699
  // eslint-disable-next-line react-hooks/rules-of-hooks
668
700
  // $FlowFixMe[react-rule-hook]
669
701
  // $FlowFixMe[react-rule-hook-conditional]
670
- useDebugValue({fragment: fragmentNode.name, data});
702
+ useDebugValue({data, fragment: fragmentNode.name});
671
703
  }
672
704
 
673
705
  return data;
@@ -47,7 +47,7 @@ export type FragmentQueryOptions = {
47
47
  networkCacheConfig?: ?CacheConfig,
48
48
  };
49
49
 
50
- type FragmentState = $ReadOnly<
50
+ type FragmentState = Readonly<
51
51
  | {kind: 'bailout', environment: IEnvironment}
52
52
  | {
53
53
  kind: 'singular',
@@ -58,7 +58,7 @@ type FragmentState = $ReadOnly<
58
58
  }
59
59
  | {
60
60
  kind: 'plural',
61
- snapshots: $ReadOnlyArray<Snapshot>,
61
+ snapshots: ReadonlyArray<Snapshot>,
62
62
  epoch: number,
63
63
  selector: ReaderSelector,
64
64
  environment: IEnvironment,
@@ -79,7 +79,7 @@ function isMissingData(state: FragmentState): boolean {
79
79
 
80
80
  function getMissingClientEdges(
81
81
  state: FragmentState,
82
- ): $ReadOnlyArray<MissingClientEdgeRequestInfo> | null {
82
+ ): ReadonlyArray<MissingClientEdgeRequestInfo> | null {
83
83
  if (state.kind === 'bailout') {
84
84
  return null;
85
85
  } else if (state.kind === 'singular') {
@@ -100,7 +100,7 @@ function getMissingClientEdges(
100
100
 
101
101
  function getSuspendingLiveResolver(
102
102
  state: FragmentState,
103
- ): $ReadOnlyArray<DataID> | null {
103
+ ): ReadonlyArray<DataID> | null {
104
104
  if (state.kind === 'bailout') {
105
105
  return null;
106
106
  } else if (state.kind === 'singular') {
@@ -122,7 +122,7 @@ function getSuspendingLiveResolver(
122
122
  function handlePotentialSnapshotErrorsForState(
123
123
  environment: IEnvironment,
124
124
  state: FragmentState,
125
- loggingContext: mixed | void,
125
+ loggingContext: unknown | void,
126
126
  ): void {
127
127
  if (state.kind === 'singular') {
128
128
  handlePotentialSnapshotErrors(
@@ -167,21 +167,21 @@ function handleMissedUpdates(
167
167
  );
168
168
  const updatedCurrentSnapshot: Snapshot = {
169
169
  data: updatedData,
170
+ fieldErrors: currentSnapshot.fieldErrors,
170
171
  isMissingData: currentSnapshot.isMissingData,
171
172
  missingClientEdges: currentSnapshot.missingClientEdges,
172
173
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
173
174
  seenRecords: currentSnapshot.seenRecords,
174
175
  selector: currentSnapshot.selector,
175
- fieldErrors: currentSnapshot.fieldErrors,
176
176
  };
177
177
  return [
178
178
  updatedData !== state.snapshot.data,
179
179
  {
180
- kind: 'singular',
181
- snapshot: updatedCurrentSnapshot,
180
+ environment: state.environment,
182
181
  epoch: currentEpoch,
182
+ kind: 'singular',
183
183
  selector: state.selector,
184
- environment: state.environment,
184
+ snapshot: updatedCurrentSnapshot,
185
185
  },
186
186
  ];
187
187
  } else {
@@ -193,12 +193,12 @@ function handleMissedUpdates(
193
193
  const updatedData = recycleNodesInto(snapshot.data, currentSnapshot.data);
194
194
  const updatedCurrentSnapshot: Snapshot = {
195
195
  data: updatedData,
196
+ fieldErrors: currentSnapshot.fieldErrors,
196
197
  isMissingData: currentSnapshot.isMissingData,
197
198
  missingClientEdges: currentSnapshot.missingClientEdges,
198
199
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
199
200
  seenRecords: currentSnapshot.seenRecords,
200
201
  selector: currentSnapshot.selector,
201
- fieldErrors: currentSnapshot.fieldErrors,
202
202
  };
203
203
  if (updatedData !== snapshot.data) {
204
204
  didMissUpdates = true;
@@ -212,22 +212,22 @@ function handleMissedUpdates(
212
212
  return [
213
213
  didMissUpdates,
214
214
  {
215
- kind: 'plural',
216
- snapshots: currentSnapshots,
215
+ environment: state.environment,
217
216
  epoch: currentEpoch,
217
+ kind: 'plural',
218
218
  selector: state.selector,
219
- environment: state.environment,
219
+ snapshots: currentSnapshots,
220
220
  },
221
221
  ];
222
222
  }
223
223
  }
224
224
 
225
- type PromiseWithDisplayName = Promise<mixed> & {displayName?: string};
225
+ type PromiseWithDisplayName = Promise<unknown> & {displayName?: string};
226
226
 
227
227
  function handleMissingClientEdge(
228
228
  environment: IEnvironment,
229
229
  parentFragmentNode: ReaderFragment,
230
- parentFragmentRef: mixed,
230
+ parentFragmentRef: unknown,
231
231
  missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
232
232
  queryOptions?: FragmentQueryOptions,
233
233
  ): [QueryResult, ?PromiseWithDisplayName] {
@@ -291,8 +291,8 @@ function subscribeToSnapshot(
291
291
  if (updates != null) {
292
292
  const [dataChanged, updatedState] = updates;
293
293
  environment.__log({
294
- name: 'useFragment.subscription.missedUpdates',
295
294
  hasDataChanges: dataChanged,
295
+ name: 'useFragment.subscription.missedUpdates',
296
296
  });
297
297
  nextState = dataChanged ? updatedState : prevState;
298
298
  } else {
@@ -300,11 +300,11 @@ function subscribeToSnapshot(
300
300
  }
301
301
  } else {
302
302
  nextState = {
303
- kind: 'singular',
304
- snapshot: latestSnapshot,
303
+ environment: state.environment,
305
304
  epoch: environment.getStore().getEpoch(),
305
+ kind: 'singular',
306
306
  selector: state.selector,
307
- environment: state.environment,
307
+ snapshot: latestSnapshot,
308
308
  };
309
309
  }
310
310
  return nextState;
@@ -333,8 +333,8 @@ function subscribeToSnapshot(
333
333
  if (updates != null) {
334
334
  const [dataChanged, updatedState] = updates;
335
335
  environment.__log({
336
- name: 'useFragment.subscription.missedUpdates',
337
336
  hasDataChanges: dataChanged,
337
+ name: 'useFragment.subscription.missedUpdates',
338
338
  });
339
339
  nextState = dataChanged ? updatedState : prevState;
340
340
  } else {
@@ -344,11 +344,11 @@ function subscribeToSnapshot(
344
344
  const updated = [...prevState.snapshots];
345
345
  updated[index] = latestSnapshot;
346
346
  nextState = {
347
- kind: 'plural',
348
- snapshots: updated,
347
+ environment: state.environment,
349
348
  epoch: environment.getStore().getEpoch(),
349
+ kind: 'plural',
350
350
  selector: state.selector,
351
- environment: state.environment,
351
+ snapshots: updated,
352
352
  };
353
353
  }
354
354
  return nextState;
@@ -368,24 +368,24 @@ function getFragmentState(
368
368
  fragmentSelector: ?ReaderSelector,
369
369
  ): FragmentState {
370
370
  if (fragmentSelector == null) {
371
- return {kind: 'bailout', environment};
371
+ return {environment, kind: 'bailout'};
372
372
  } else if (fragmentSelector.kind === 'PluralReaderSelector') {
373
373
  // Note that if fragmentRef is an empty array, fragmentSelector will be null so we'll hit the above case.
374
374
  // Null is returned by getSelector if fragmentRef has no non-null items.
375
375
  return {
376
- kind: 'plural',
377
- snapshots: fragmentSelector.selectors.map(s => environment.lookup(s)),
376
+ environment,
378
377
  epoch: environment.getStore().getEpoch(),
378
+ kind: 'plural',
379
379
  selector: fragmentSelector,
380
- environment: environment,
380
+ snapshots: fragmentSelector.selectors.map(s => environment.lookup(s)),
381
381
  };
382
382
  } else {
383
383
  return {
384
- kind: 'singular',
385
- snapshot: environment.lookup(fragmentSelector),
384
+ environment,
386
385
  epoch: environment.getStore().getEpoch(),
386
+ kind: 'singular',
387
387
  selector: fragmentSelector,
388
- environment: environment,
388
+ snapshot: environment.lookup(fragmentSelector),
389
389
  };
390
390
  }
391
391
  }
@@ -393,7 +393,7 @@ function getFragmentState(
393
393
  // fragmentNode cannot change during the lifetime of the component, though fragmentRef may change.
394
394
  hook useFragmentInternal_EXPERIMENTAL(
395
395
  fragmentNode: ReaderFragment,
396
- fragmentRef: mixed,
396
+ fragmentRef: unknown,
397
397
  hookDisplayName: string,
398
398
  queryOptions?: FragmentQueryOptions,
399
399
  ): ?SelectorData | Array<?SelectorData> {
@@ -501,7 +501,7 @@ hook useFragmentInternal_EXPERIMENTAL(
501
501
  let clientEdgeQueries;
502
502
  const activeRequestPromises: Array<PromiseWithDisplayName> = [];
503
503
  if (missingClientEdges?.length) {
504
- clientEdgeQueries = ([]: Array<QueryResult>);
504
+ clientEdgeQueries = [] as Array<QueryResult>;
505
505
  for (const edge of missingClientEdges) {
506
506
  const [queryResult, requestPromise] = handleMissingClientEdge(
507
507
  environment,
@@ -520,6 +520,18 @@ hook useFragmentInternal_EXPERIMENTAL(
520
520
  }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
521
521
 
522
522
  if (activeRequestPromises.length) {
523
+ invariant(fragmentSelector != null, 'refinement, see invariants above');
524
+ const fragmentOwner =
525
+ fragmentSelector.kind === 'PluralReaderSelector'
526
+ ? fragmentSelector.selectors[0].owner
527
+ : fragmentSelector.owner;
528
+ environment.__log({
529
+ name: 'suspense.client_edge',
530
+ fragment: fragmentNode,
531
+ fragmentOwner,
532
+ // $FlowFixMe[react-rule-unsafe-ref]
533
+ isMount: committedFragmentSelectorRef.current === false,
534
+ });
523
535
  const allPromises = Promise.all(activeRequestPromises);
524
536
  // $FlowExpectedError[prop-missing] Expando to annotate Promises.
525
537
  allPromises.displayName = `RelayClientEdge(${activeRequestPromises
@@ -552,6 +564,19 @@ hook useFragmentInternal_EXPERIMENTAL(
552
564
  // Suspend if a Live Resolver within this fragment is in a suspended state:
553
565
  const suspendingLiveResolvers = getSuspendingLiveResolver(state);
554
566
  if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
567
+ invariant(fragmentSelector != null, 'refinement, see invariants above');
568
+ const fragmentOwner =
569
+ fragmentSelector.kind === 'PluralReaderSelector'
570
+ ? fragmentSelector.selectors[0].owner
571
+ : fragmentSelector.owner;
572
+ environment.__log({
573
+ name: 'suspense.resolver',
574
+ fragment: fragmentNode,
575
+ fragmentOwner,
576
+ // $FlowFixMe[react-rule-unsafe-ref]
577
+ isMount: committedFragmentSelectorRef.current === false,
578
+ suspendingLiveResolvers,
579
+ });
555
580
  const promise = Promise.all(
556
581
  suspendingLiveResolvers.map(liveStateID => {
557
582
  // $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
@@ -569,8 +594,7 @@ hook useFragmentInternal_EXPERIMENTAL(
569
594
  if (
570
595
  RelayFeatureFlags.ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE ||
571
596
  environment !== previousEnvironment ||
572
- // $FlowFixMe[sketchy-null-bool]
573
- !committedFragmentSelectorRef.current ||
597
+ committedFragmentSelectorRef.current === false ||
574
598
  // $FlowFixMe[react-rule-unsafe-ref]
575
599
  !areEqualSelectors(committedFragmentSelectorRef.current, fragmentSelector)
576
600
  ) {
@@ -585,6 +609,14 @@ hook useFragmentInternal_EXPERIMENTAL(
585
609
  fragmentOwner,
586
610
  );
587
611
  if (pendingOperationsResult) {
612
+ environment.__log({
613
+ name: 'suspense.missing_data',
614
+ fragment: fragmentNode,
615
+ fragmentOwner,
616
+ // $FlowFixMe[react-rule-unsafe-ref]
617
+ isMount: committedFragmentSelectorRef.current === false,
618
+ pendingOperations: pendingOperationsResult.pendingOperations,
619
+ });
588
620
  throw pendingOperationsResult.promise;
589
621
  }
590
622
  }
@@ -608,15 +640,20 @@ hook useFragmentInternal_EXPERIMENTAL(
608
640
  // or detaches (<Activity> going hidden), and then re-subscribes when the component
609
641
  // re-attaches (<Activity> going visible). These cases wouldn't fire the
610
642
  // "update" effect because the state and environment don't change.
611
- const storeSubscriptionRef = useRef<{
612
- dispose: () => void,
613
- selector: ?ReaderSelector,
614
- environment: IEnvironment,
615
- } | null>(null);
643
+ const storeSubscriptionRef = useRef<
644
+ | {
645
+ kind: 'initialized',
646
+ dispose: () => void,
647
+ selector: ?ReaderSelector,
648
+ environment: IEnvironment,
649
+ }
650
+ | {kind: 'missed-updates'}
651
+ | {kind: 'uninitialized'},
652
+ >({kind: 'uninitialized'});
616
653
  // $FlowFixMe[react-rule-hook] - the condition is static
617
654
  useEffect(() => {
618
655
  const storeSubscription = storeSubscriptionRef.current;
619
- if (storeSubscription != null) {
656
+ if (storeSubscription.kind === 'initialized') {
620
657
  if (
621
658
  state.environment === storeSubscription.environment &&
622
659
  state.selector === storeSubscription.selector
@@ -626,6 +663,7 @@ hook useFragmentInternal_EXPERIMENTAL(
626
663
  } else {
627
664
  // The selector has changed, so we need to dispose of the previous subscription
628
665
  storeSubscription.dispose();
666
+ storeSubscriptionRef.current = {kind: 'uninitialized'};
629
667
  }
630
668
  }
631
669
  if (state.kind === 'bailout') {
@@ -650,7 +688,11 @@ hook useFragmentInternal_EXPERIMENTAL(
650
688
  // to using the latest snapshot to subscribe.
651
689
  if (didMissUpdates) {
652
690
  setState(updatedState);
691
+ storeSubscriptionRef.current = {kind: 'missed-updates'};
653
692
  // We missed updates, we're going to render again anyway so wait until then to subscribe
693
+ // Setting the ref to kind: missed-updates ensures the second useEffect (simulating the
694
+ // setup/teardown part of the crud effect) will not set up the subscription w the stale
695
+ // state
654
696
  return;
655
697
  }
656
698
  stateForSubscription = updatedState;
@@ -662,23 +704,30 @@ hook useFragmentInternal_EXPERIMENTAL(
662
704
  );
663
705
  storeSubscriptionRef.current = {
664
706
  dispose,
665
- selector: state.selector,
666
707
  environment: state.environment,
708
+ kind: 'initialized',
709
+ selector: state.selector,
667
710
  };
668
711
  }, [state]);
669
712
  // $FlowFixMe[react-rule-hook] - the condition is static
670
713
  useEffect(() => {
671
- if (storeSubscriptionRef.current == null && state.kind !== 'bailout') {
714
+ if (
715
+ storeSubscriptionRef.current.kind === 'uninitialized' &&
716
+ state.kind !== 'bailout'
717
+ ) {
672
718
  const dispose = subscribeToSnapshot(state.environment, state, setState);
673
719
  storeSubscriptionRef.current = {
674
720
  dispose,
675
- selector: state.selector,
676
721
  environment: state.environment,
722
+ kind: 'initialized',
723
+ selector: state.selector,
677
724
  };
678
725
  }
679
726
  return () => {
680
- storeSubscriptionRef.current?.dispose();
681
- storeSubscriptionRef.current = null;
727
+ if (storeSubscriptionRef.current.kind === 'initialized') {
728
+ storeSubscriptionRef.current.dispose();
729
+ }
730
+ storeSubscriptionRef.current = {kind: 'uninitialized'};
682
731
  };
683
732
  // NOTE: this intentionally has no dependencies, see above comment about
684
733
  // simulating a CRUD effect
@@ -747,7 +796,7 @@ hook useFragmentInternal_EXPERIMENTAL(
747
796
  // eslint-disable-next-line react-hooks/rules-of-hooks
748
797
  // $FlowFixMe[react-rule-hook]
749
798
  // $FlowFixMe[react-rule-hook-conditional]
750
- useDebugValue({fragment: fragmentNode.name, data});
799
+ useDebugValue({data, fragment: fragmentNode.name});
751
800
  }
752
801
 
753
802
  return data;
@@ -25,7 +25,7 @@ const {useEffect, useState, useMemo} = React;
25
25
 
26
26
  hook useIsOperationNodeActive(
27
27
  fragmentNode: ReaderFragment,
28
- fragmentRef: mixed,
28
+ fragmentRef: unknown,
29
29
  ): boolean {
30
30
  const environment = useRelayEnvironment();
31
31
  const observable = useMemo(() => {
@@ -18,7 +18,7 @@ const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
18
18
  const {getFragment} = require('relay-runtime');
19
19
 
20
20
  hook useIsParentQueryActive<
21
- TKey: ?{+$data?: mixed, +$fragmentSpreads: FragmentType, ...},
21
+ TKey extends ?{+$data?: unknown, +$fragmentSpreads: FragmentType, ...},
22
22
  >(
23
23
  fragmentInput: GraphQLTaggedNode,
24
24
  fragmentRef: TKey,
@@ -11,6 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ import type {LazyLoadQueryNodeParamsWithQuery} from './useLazyLoadQueryNode';
14
15
  import type {
15
16
  CacheConfig,
16
17
  FetchPolicy,
@@ -29,7 +30,10 @@ const {
29
30
  // This separate type export is only needed as long as we are injecting
30
31
  // a separate hooks implementation in ./HooksImplementation -- it can
31
32
  // be removed after we stop doing that.
32
- export type UseLazyLoadQueryHookType = hook <TVariables: Variables, TData>(
33
+ export type UseLazyLoadQueryHookType = hook <
34
+ TVariables extends Variables,
35
+ TData,
36
+ >(
33
37
  gqlQuery: Query<TVariables, TData>,
34
38
  variables: TVariables,
35
39
  options?: Options,
@@ -85,7 +89,7 @@ type Options = {
85
89
  * @returns - `data`: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
86
90
  * - 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
91
  */
88
- hook useLazyLoadQuery<TVariables: Variables, TData>(
92
+ hook useLazyLoadQuery<TVariables extends Variables, TData>(
89
93
  /**
90
94
  * GraphQL query specified using a `graphql` template literal.
91
95
  */
@@ -108,7 +112,10 @@ hook useLazyLoadQuery<TVariables: Variables, TData>(
108
112
  ? options.networkCacheConfig
109
113
  : {force: true},
110
114
  );
111
- const data = useLazyLoadQueryNode<$FlowFixMe>({
115
+ const data = useLazyLoadQueryNode<
116
+ $FlowFixMe,
117
+ LazyLoadQueryNodeParamsWithQuery,
118
+ >({
112
119
  componentDisplayName: 'useLazyLoadQuery()',
113
120
  fetchKey: options?.fetchKey,
114
121
  fetchObservable: fetchQuery(environment, query),