react-relay 14.1.0 → 16.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (196) hide show
  1. package/ReactRelayContainerUtils.js.flow +1 -0
  2. package/ReactRelayContext.js +1 -1
  3. package/ReactRelayContext.js.flow +1 -0
  4. package/ReactRelayFragmentContainer.js.flow +6 -2
  5. package/ReactRelayFragmentMockRenderer.js.flow +1 -0
  6. package/ReactRelayLocalQueryRenderer.js.flow +5 -3
  7. package/ReactRelayPaginationContainer.js.flow +21 -12
  8. package/ReactRelayQueryFetcher.js.flow +20 -10
  9. package/ReactRelayQueryRenderer.js.flow +15 -11
  10. package/ReactRelayQueryRendererContext.js.flow +1 -0
  11. package/ReactRelayRefetchContainer.js.flow +9 -5
  12. package/ReactRelayTestMocker.js.flow +3 -1
  13. package/ReactRelayTypes.js.flow +2 -0
  14. package/RelayContext.js.flow +1 -0
  15. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +2 -1
  16. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +1 -0
  17. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +1 -0
  18. package/__flowtests__/RelayModern-flowtest.js.flow +1 -0
  19. package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +1 -0
  20. package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +1 -0
  21. package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +1 -0
  22. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +1 -0
  23. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +3 -1
  24. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +3 -1
  25. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +4 -2
  26. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +3 -1
  27. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +4 -2
  28. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +3 -1
  29. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +4 -2
  30. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +4 -2
  31. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +3 -1
  32. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +3 -1
  33. package/assertFragmentMap.js.flow +1 -0
  34. package/buildReactRelayContainer.js.flow +10 -6
  35. package/getRootVariablesForFragments.js.flow +1 -1
  36. package/hooks.js +1 -1
  37. package/hooks.js.flow +4 -0
  38. package/index.js +1 -1
  39. package/index.js.flow +4 -0
  40. package/isRelayEnvironment.js.flow +1 -0
  41. package/jest-react/enqueueTask.js.flow +1 -1
  42. package/jest-react/index.js.flow +1 -1
  43. package/jest-react/internalAct.js.flow +1 -1
  44. package/legacy.js +1 -1
  45. package/legacy.js.flow +1 -0
  46. package/lib/ReactRelayContainerUtils.js +0 -11
  47. package/lib/ReactRelayContext.js +1 -12
  48. package/lib/ReactRelayFragmentContainer.js +23 -122
  49. package/lib/ReactRelayFragmentMockRenderer.js +0 -12
  50. package/lib/ReactRelayLocalQueryRenderer.js +12 -41
  51. package/lib/ReactRelayPaginationContainer.js +45 -341
  52. package/lib/ReactRelayQueryFetcher.js +36 -111
  53. package/lib/ReactRelayQueryRenderer.js +29 -137
  54. package/lib/ReactRelayQueryRendererContext.js +0 -10
  55. package/lib/ReactRelayRefetchContainer.js +33 -166
  56. package/lib/ReactRelayTestMocker.js +18 -128
  57. package/lib/ReactRelayTypes.js +0 -9
  58. package/lib/RelayContext.js +0 -23
  59. package/lib/assertFragmentMap.js +0 -16
  60. package/lib/buildReactRelayContainer.js +7 -41
  61. package/lib/getRootVariablesForFragments.js +2 -19
  62. package/lib/hooks.js +3 -30
  63. package/lib/index.js +3 -39
  64. package/lib/isRelayEnvironment.js +1 -16
  65. package/lib/jest-react/enqueueTask.js +1 -25
  66. package/lib/jest-react/index.js +0 -1
  67. package/lib/jest-react/internalAct.js +2 -51
  68. package/lib/legacy.js +0 -20
  69. package/lib/multi-actor/ActorChange.js +0 -14
  70. package/lib/multi-actor/index.js +0 -10
  71. package/lib/multi-actor/useRelayActorEnvironment.js +2 -16
  72. package/lib/relay-hooks/EntryPointContainer.react.js +7 -23
  73. package/lib/relay-hooks/EntryPointTypes.flow.js +0 -10
  74. package/lib/relay-hooks/FragmentResource.js +130 -280
  75. package/lib/relay-hooks/HooksImplementation.js +0 -14
  76. package/lib/relay-hooks/InternalLogger.js +0 -11
  77. package/lib/relay-hooks/LRUCache.js +0 -39
  78. package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +27 -65
  79. package/lib/relay-hooks/MatchContainer.js +9 -111
  80. package/lib/relay-hooks/NestedRelayEntryPointBuilderUtils.js +9 -0
  81. package/lib/relay-hooks/ProfilerContext.js +0 -14
  82. package/lib/relay-hooks/QueryResource.js +14 -149
  83. package/lib/relay-hooks/RelayEnvironmentProvider.js +3 -17
  84. package/lib/relay-hooks/SuspenseResource.js +2 -59
  85. package/lib/relay-hooks/loadEntryPoint.js +10 -45
  86. package/lib/relay-hooks/loadQuery.js +29 -169
  87. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +8 -58
  88. package/lib/relay-hooks/prepareEntryPoint_DEPRECATED.js +6 -24
  89. package/lib/relay-hooks/react-cache/RelayReactCache.js +4 -20
  90. package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +13 -102
  91. package/lib/relay-hooks/react-cache/readFragmentInternal_REACT_CACHE.js +18 -75
  92. package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +79 -222
  93. package/lib/relay-hooks/react-cache/useFragment_REACT_CACHE.js +3 -27
  94. package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +11 -33
  95. package/lib/relay-hooks/react-cache/usePaginationFragment_REACT_CACHE.js +62 -85
  96. package/lib/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js +20 -63
  97. package/lib/relay-hooks/react-cache/useRefetchableFragmentInternal_REACT_CACHE.js +53 -179
  98. package/lib/relay-hooks/react-cache/useRefetchableFragment_REACT_CACHE.js +5 -27
  99. package/lib/relay-hooks/useBlockingPaginationFragment.js +58 -121
  100. package/lib/relay-hooks/useClientQuery.js +0 -21
  101. package/lib/relay-hooks/useEntryPointLoader.js +12 -100
  102. package/lib/relay-hooks/useFetchTrackingRef.js +6 -33
  103. package/lib/relay-hooks/useFragment.js +5 -32
  104. package/lib/relay-hooks/useFragmentNode.js +14 -55
  105. package/lib/relay-hooks/useIsMountedRef.js +2 -14
  106. package/lib/relay-hooks/useIsOperationNodeActive.js +6 -29
  107. package/lib/relay-hooks/useIsParentQueryActive.js +1 -15
  108. package/lib/relay-hooks/useLazyLoadQuery.js +2 -23
  109. package/lib/relay-hooks/useLazyLoadQueryNode.js +18 -63
  110. package/lib/relay-hooks/useLoadMoreFunction.js +44 -100
  111. package/lib/relay-hooks/useMemoOperationDescriptor.js +4 -23
  112. package/lib/relay-hooks/useMemoVariables.js +8 -43
  113. package/lib/relay-hooks/useMutation.js +6 -34
  114. package/lib/relay-hooks/usePaginationFragment.js +49 -89
  115. package/lib/relay-hooks/usePreloadedQuery.js +10 -54
  116. package/lib/relay-hooks/useQueryLoader.js +18 -116
  117. package/lib/relay-hooks/useRefetchableFragment.js +4 -30
  118. package/lib/relay-hooks/useRefetchableFragmentNode.js +58 -184
  119. package/lib/relay-hooks/useRelayEnvironment.js +2 -16
  120. package/lib/relay-hooks/useStaticFragmentNodeWarning.js +2 -20
  121. package/lib/relay-hooks/useSubscribeToInvalidationState.js +3 -28
  122. package/lib/relay-hooks/useSubscription.js +3 -22
  123. package/lib/relay-hooks/useUnsafeRef_DEPRECATED.js +12 -0
  124. package/multi-actor/ActorChange.js.flow +1 -1
  125. package/multi-actor/index.js.flow +1 -1
  126. package/multi-actor/useRelayActorEnvironment.js.flow +2 -2
  127. package/package.json +2 -2
  128. package/react-relay-hooks.js +2 -2
  129. package/react-relay-hooks.min.js +2 -2
  130. package/react-relay-legacy.js +2 -2
  131. package/react-relay-legacy.min.js +2 -2
  132. package/react-relay.js +2 -2
  133. package/react-relay.min.js +2 -2
  134. package/relay-hooks/EntryPointContainer.react.js.flow +6 -1
  135. package/relay-hooks/EntryPointTypes.flow.js.flow +23 -20
  136. package/relay-hooks/FragmentResource.js.flow +148 -34
  137. package/relay-hooks/HooksImplementation.js.flow +1 -1
  138. package/relay-hooks/InternalLogger.js.flow +1 -1
  139. package/relay-hooks/LRUCache.js.flow +1 -1
  140. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +19 -10
  141. package/relay-hooks/MatchContainer.js.flow +1 -1
  142. package/relay-hooks/NestedRelayEntryPointBuilderUtils.js.flow +51 -0
  143. package/relay-hooks/ProfilerContext.js.flow +1 -1
  144. package/relay-hooks/QueryResource.js.flow +25 -5
  145. package/relay-hooks/RelayEnvironmentProvider.js.flow +2 -2
  146. package/relay-hooks/SuspenseResource.js.flow +1 -1
  147. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +3 -1
  148. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +9 -7
  149. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +3 -1
  150. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +3 -1
  151. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +40 -33
  152. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +1 -1
  153. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +38 -32
  154. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +20 -18
  155. package/relay-hooks/__flowtests__/utils.js.flow +13 -2
  156. package/relay-hooks/loadEntryPoint.js.flow +15 -8
  157. package/relay-hooks/loadQuery.js.flow +32 -8
  158. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +5 -6
  159. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +17 -10
  160. package/relay-hooks/react-cache/RelayReactCache.js.flow +1 -1
  161. package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +4 -4
  162. package/relay-hooks/react-cache/readFragmentInternal_REACT_CACHE.js.flow +5 -4
  163. package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +32 -14
  164. package/relay-hooks/react-cache/useFragment_REACT_CACHE.js.flow +4 -10
  165. package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +1 -1
  166. package/relay-hooks/react-cache/usePaginationFragment_REACT_CACHE.js.flow +39 -49
  167. package/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js.flow +1 -2
  168. package/relay-hooks/react-cache/useRefetchableFragmentInternal_REACT_CACHE.js.flow +29 -16
  169. package/relay-hooks/react-cache/useRefetchableFragment_REACT_CACHE.js.flow +17 -33
  170. package/relay-hooks/useBlockingPaginationFragment.js.flow +85 -58
  171. package/relay-hooks/useClientQuery.js.flow +3 -3
  172. package/relay-hooks/useEntryPointLoader.js.flow +10 -6
  173. package/relay-hooks/useFetchTrackingRef.js.flow +5 -4
  174. package/relay-hooks/useFragment.js.flow +2 -2
  175. package/relay-hooks/useFragmentNode.js.flow +7 -6
  176. package/relay-hooks/useIsMountedRef.js.flow +1 -1
  177. package/relay-hooks/useIsOperationNodeActive.js.flow +1 -1
  178. package/relay-hooks/useIsParentQueryActive.js.flow +1 -1
  179. package/relay-hooks/useLazyLoadQuery.js.flow +2 -2
  180. package/relay-hooks/useLazyLoadQueryNode.js.flow +2 -2
  181. package/relay-hooks/useLoadMoreFunction.js.flow +27 -16
  182. package/relay-hooks/useMemoOperationDescriptor.js.flow +3 -3
  183. package/relay-hooks/useMemoVariables.js.flow +13 -29
  184. package/relay-hooks/useMutation.js.flow +30 -13
  185. package/relay-hooks/usePaginationFragment.js.flow +55 -54
  186. package/relay-hooks/usePreloadedQuery.js.flow +47 -22
  187. package/relay-hooks/useQueryLoader.js.flow +78 -21
  188. package/relay-hooks/useRefetchableFragment.js.flow +65 -33
  189. package/relay-hooks/useRefetchableFragmentNode.js.flow +38 -17
  190. package/relay-hooks/useRelayEnvironment.js.flow +2 -2
  191. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +3 -3
  192. package/relay-hooks/useSubscribeToInvalidationState.js.flow +2 -2
  193. package/relay-hooks/useSubscription.js.flow +1 -1
  194. package/relay-hooks/useUnsafeRef_DEPRECATED.js.flow +25 -0
  195. package/lib/readContext.js +0 -27
  196. package/readContext.js.flow +0 -29
@@ -4,13 +4,13 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @emails oncall+relay
8
7
  * @flow strict-local
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
13
-
13
+ import type {OperationType} from '../../relay-runtime/util/RelayRuntimeTypes';
14
14
  import type {
15
15
  EntryPoint,
16
16
  EntryPointComponent,
@@ -40,12 +40,15 @@ function prepareEntryPoint<
40
40
  ): void {
41
41
  // Start loading the code for the entrypoint
42
42
  if (entryPoint.root.getModuleIfRequired() == null) {
43
+ // $FlowFixMe[unused-promise]
43
44
  entryPoint.root.load();
44
45
  }
45
46
  const preloadProps = entryPoint.getPreloadProps(entryPointParams);
46
47
  const {queries, entryPoints} = preloadProps;
47
- const preloadedQueries: $Shape<TPreloadedQueries> = {};
48
- const preloadedEntryPoints: $Shape<TPreloadedEntryPoints> = {};
48
+ // $FlowFixMe[incompatible-type]
49
+ const preloadedQueries: Partial<TPreloadedQueries> = {};
50
+ // $FlowFixMe[incompatible-type]
51
+ const preloadedEntryPoints: Partial<TPreloadedEntryPoints> = {};
49
52
  if (queries != null) {
50
53
  const queriesPropNames = Object.keys(queries);
51
54
  queriesPropNames.forEach(queryPropName => {
@@ -56,7 +59,7 @@ function prepareEntryPoint<
56
59
  environmentProviderOptions,
57
60
  );
58
61
 
59
- preloadedQueries[queryPropName] = preloadQuery(
62
+ preloadedQueries[queryPropName] = preloadQuery<OperationType, mixed>(
60
63
  environment,
61
64
  parameters,
62
65
  variables,
@@ -75,11 +78,15 @@ function prepareEntryPoint<
75
78
  }
76
79
  const {entryPoint: nestedEntryPoint, entryPointParams: nestedParams} =
77
80
  entryPointDescription;
78
- preloadedEntryPoints[entryPointPropName] = prepareEntryPoint(
79
- environmentProvider,
80
- nestedEntryPoint,
81
- nestedParams,
82
- );
81
+ preloadedEntryPoints[entryPointPropName] = prepareEntryPoint<
82
+ TEntryPointParams,
83
+ TPreloadedQueries,
84
+ TPreloadedEntryPoints,
85
+ TRuntimeProps,
86
+ TExtraProps,
87
+ TEntryPointComponent,
88
+ TEntryPoint,
89
+ >(environmentProvider, nestedEntryPoint, nestedParams);
83
90
  });
84
91
  }
85
92
  }
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -25,8 +25,8 @@ const SuspenseResource = require('../SuspenseResource');
25
25
  const {getCacheForType, getCacheSignal} = require('./RelayReactCache');
26
26
  const invariant = require('invariant');
27
27
  const {
28
- RelayFeatureFlags,
29
28
  __internal: {fetchQuery: fetchQueryInternal},
29
+ RelayFeatureFlags,
30
30
  } = require('relay-runtime');
31
31
  const warning = require('warning');
32
32
 
@@ -127,7 +127,7 @@ const noopOnCommit = () => {
127
127
  return () => undefined;
128
128
  };
129
129
 
130
- const noopPromise = new Promise(() => {});
130
+ const noopPromise = new Promise<void>(() => {});
131
131
 
132
132
  function getQueryCacheKey(
133
133
  operation: OperationDescriptor,
@@ -375,7 +375,7 @@ function executeOperationAndKeepUpToDate(
375
375
  customFetchObservable?: Observable<GraphQLResponse>,
376
376
  ) {
377
377
  let resolvePromise;
378
- const promise = new Promise(r => {
378
+ const promise = new Promise<void>(r => {
379
379
  resolvePromise = r;
380
380
  });
381
381
  // $FlowExpectedError[prop-missing] Expando to annotate Promises.
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -27,6 +27,7 @@ const {getQueryResourceForEnvironment} = require('../QueryResource');
27
27
  const invariant = require('invariant');
28
28
  const {
29
29
  __internal: {fetchQuery: fetchQueryInternal},
30
+ RelayFeatureFlags,
30
31
  createOperationDescriptor,
31
32
  getPendingOperationsForFragment,
32
33
  getSelector,
@@ -64,7 +65,7 @@ function getMissingClientEdges(
64
65
  } else if (state.kind === 'singular') {
65
66
  return state.snapshot.missingClientEdges ?? null;
66
67
  } else {
67
- let edges = null;
68
+ let edges: null | Array<MissingClientEdgeRequestInfo> = null;
68
69
  for (const snapshot of state.snapshots) {
69
70
  if (snapshot.missingClientEdges) {
70
71
  edges = edges ?? [];
@@ -223,7 +224,7 @@ function readFragmentInternal_REACT_CACHE(
223
224
  if (fragmentNode.metadata?.hasClientEdges === true) {
224
225
  const missingClientEdges = getMissingClientEdges(state);
225
226
  if (missingClientEdges?.length) {
226
- clientEdgeQueries = [];
227
+ clientEdgeQueries = ([]: Array<QueryResult>);
227
228
  for (const edge of missingClientEdges) {
228
229
  clientEdgeQueries.push(
229
230
  handleMissingClientEdge(
@@ -268,7 +269,7 @@ function readFragmentInternal_REACT_CACHE(
268
269
  data = state.snapshots.map(s => s.data);
269
270
  }
270
271
 
271
- if (__DEV__) {
272
+ if (RelayFeatureFlags.LOG_MISSING_RECORDS_IN_PROD || __DEV__) {
272
273
  if (
273
274
  fragmentRef != null &&
274
275
  (data === undefined ||
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -32,6 +32,7 @@ const invariant = require('invariant');
32
32
  const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
33
33
  const {
34
34
  __internal: {fetchQuery: fetchQueryInternal},
35
+ RelayFeatureFlags,
35
36
  areEqualSelectors,
36
37
  createOperationDescriptor,
37
38
  getPendingOperationsForFragment,
@@ -73,7 +74,7 @@ function getMissingClientEdges(
73
74
  } else if (state.kind === 'singular') {
74
75
  return state.snapshot.missingClientEdges ?? null;
75
76
  } else {
76
- let edges = null;
77
+ let edges: null | Array<MissingClientEdgeRequestInfo> = null;
77
78
  for (const snapshot of state.snapshots) {
78
79
  if (snapshot.missingClientEdges) {
79
80
  edges = edges ?? [];
@@ -94,7 +95,7 @@ function getSuspendingLiveResolver(
94
95
  } else if (state.kind === 'singular') {
95
96
  return state.snapshot.missingLiveResolverFields ?? null;
96
97
  } else {
97
- let missingFields = null;
98
+ let missingFields: null | Array<MissingLiveResolverField> = null;
98
99
  for (const snapshot of state.snapshots) {
99
100
  if (snapshot.missingLiveResolverFields) {
100
101
  missingFields = missingFields ?? [];
@@ -256,7 +257,17 @@ function subscribeToSnapshot(
256
257
  prevState.kind !== 'singular' ||
257
258
  prevState.snapshot.selector !== latestSnapshot.selector
258
259
  ) {
259
- return prevState;
260
+ const updates = handleMissedUpdates(environment, prevState);
261
+ if (updates != null) {
262
+ const [dataChanged, nextState] = updates;
263
+ environment.__log({
264
+ name: 'useFragment.subscription.missedUpdates',
265
+ hasDataChanges: dataChanged,
266
+ });
267
+ return dataChanged ? nextState : prevState;
268
+ } else {
269
+ return prevState;
270
+ }
260
271
  }
261
272
  return {
262
273
  kind: 'singular',
@@ -279,7 +290,17 @@ function subscribeToSnapshot(
279
290
  prevState.kind !== 'plural' ||
280
291
  prevState.snapshots[index]?.selector !== latestSnapshot.selector
281
292
  ) {
282
- return prevState;
293
+ const updates = handleMissedUpdates(environment, prevState);
294
+ if (updates != null) {
295
+ const [dataChanged, nextState] = updates;
296
+ environment.__log({
297
+ name: 'useFragment.subscription.missedUpdates',
298
+ hasDataChanges: dataChanged,
299
+ });
300
+ return dataChanged ? nextState : prevState;
301
+ } else {
302
+ return prevState;
303
+ }
283
304
  }
284
305
  const updated = [...prevState.snapshots];
285
306
  updated[index] = latestSnapshot;
@@ -328,7 +349,6 @@ function useFragmentInternal_REACT_CACHE(
328
349
  fragmentRef: mixed,
329
350
  hookDisplayName: string,
330
351
  queryOptions?: FragmentQueryOptions,
331
- fragmentKey?: string,
332
352
  ): ?SelectorData | Array<?SelectorData> {
333
353
  const fragmentSelector = useMemo(
334
354
  () => getSelector(fragmentNode, fragmentRef),
@@ -343,7 +363,6 @@ function useFragmentInternal_REACT_CACHE(
343
363
  'Relay: Expected fragment pointer%s for fragment `%s` to be ' +
344
364
  'an array, instead got `%s`. Remove `@relay(plural: true)` ' +
345
365
  'from fragment `%s` to allow the prop to be an object.',
346
- fragmentKey != null ? ` for key \`${fragmentKey}\`` : '',
347
366
  fragmentNode.name,
348
367
  typeof fragmentRef,
349
368
  fragmentNode.name,
@@ -354,7 +373,6 @@ function useFragmentInternal_REACT_CACHE(
354
373
  'Relay: Expected fragment pointer%s for fragment `%s` not to be ' +
355
374
  'an array, instead got `%s`. Add `@relay(plural: true)` ' +
356
375
  'to fragment `%s` to allow the prop to be an array.',
357
- fragmentKey != null ? ` for key \`${fragmentKey}\`` : '',
358
376
  fragmentNode.name,
359
377
  typeof fragmentRef,
360
378
  fragmentNode.name,
@@ -378,7 +396,6 @@ function useFragmentInternal_REACT_CACHE(
378
396
  fragmentNode.name,
379
397
  hookDisplayName,
380
398
  fragmentNode.name,
381
- fragmentKey == null ? 'a fragment reference' : `the \`${fragmentKey}\``,
382
399
  hookDisplayName,
383
400
  );
384
401
 
@@ -438,7 +455,7 @@ function useFragmentInternal_REACT_CACHE(
438
455
  // eslint-disable-next-line no-shadow
439
456
  let clientEdgeQueries;
440
457
  if (missingClientEdges?.length) {
441
- clientEdgeQueries = [];
458
+ clientEdgeQueries = ([]: Array<QueryResult>);
442
459
  for (const edge of missingClientEdges) {
443
460
  clientEdgeQueries.push(
444
461
  handleMissingClientEdge(
@@ -505,11 +522,12 @@ function useFragmentInternal_REACT_CACHE(
505
522
  throw pendingOperationsResult.promise;
506
523
  }
507
524
  }
508
- // Report required fields only if we're not suspending, since that means
509
- // they're missing even though we are out of options for possibly fetching them:
510
- handlePotentialSnapshotErrorsForState(environment, state);
511
525
  }
512
526
 
527
+ // Report required fields only if we're not suspending, since that means
528
+ // they're missing even though we are out of options for possibly fetching them:
529
+ handlePotentialSnapshotErrorsForState(environment, state);
530
+
513
531
  useEffect(() => {
514
532
  // Check for updates since the state was rendered
515
533
  let currentState = subscribedState;
@@ -565,7 +583,7 @@ function useFragmentInternal_REACT_CACHE(
565
583
  data = state.snapshot.data;
566
584
  }
567
585
 
568
- if (__DEV__) {
586
+ if (RelayFeatureFlags.LOG_MISSING_RECORDS_IN_PROD || __DEV__) {
569
587
  if (
570
588
  fragmentRef != null &&
571
589
  (data === undefined ||
@@ -4,9 +4,9 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @emails oncall+relay
8
7
  * @flow strict-local
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -30,23 +30,17 @@ declare function useFragment<TFragmentType: FragmentType, TData>(
30
30
  key: HasSpread<TFragmentType>,
31
31
  ): TData;
32
32
 
33
- // if the key is nullable, return nullable value
34
- declare function useFragment<TFragmentType: FragmentType, TData>(
35
- fragment: Fragment<TFragmentType, TData>,
36
- key: ?HasSpread<TFragmentType>,
37
- ): ?TData;
38
-
39
33
  // if the key is a non-nullable array of keys, return non-nullable array
40
34
  declare function useFragment<TFragmentType: FragmentType, TData>(
41
35
  fragment: Fragment<TFragmentType, TData>,
42
36
  key: $ReadOnlyArray<HasSpread<TFragmentType>>,
43
37
  ): TData;
44
38
 
45
- // if the key is a nullable array of keys, return nullable array
39
+ // if the key is null/void, return null/void value
46
40
  declare function useFragment<TFragmentType: FragmentType, TData>(
47
41
  fragment: Fragment<TFragmentType, TData>,
48
- key: ?$ReadOnlyArray<HasSpread<TFragmentType>>,
49
- ): ?TData;
42
+ key: null | void,
43
+ ): null | void;
50
44
 
51
45
  function useFragment(fragment: GraphQLTaggedNode, key: mixed): mixed {
52
46
  // We need to use this hook in order to be able to track if
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -4,24 +4,26 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @emails oncall+relay
8
7
  * @flow strict-local
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
13
13
 
14
14
  import type {LoadMoreFn, UseLoadMoreFunctionArgs} from '../useLoadMoreFunction';
15
- import type {RefetchFnDynamic} from './useRefetchableFragmentInternal_REACT_CACHE';
15
+ import type {ReturnType} from '../usePaginationFragment';
16
+ import type {Options} from './useRefetchableFragmentInternal_REACT_CACHE';
16
17
  import type {
17
18
  FragmentType,
18
19
  GraphQLResponse,
19
- GraphQLTaggedNode,
20
20
  Observer,
21
- OperationType,
21
+ RefetchableFragment,
22
+ Variables,
22
23
  } from 'relay-runtime';
23
24
 
24
25
  const useLoadMoreFunction = require('../useLoadMoreFunction');
26
+ const useRelayEnvironment = require('../useRelayEnvironment');
25
27
  const useStaticFragmentNodeWarning = require('../useStaticFragmentNodeWarning');
26
28
  const useRefetchableFragmentInternal = require('./useRefetchableFragmentInternal_REACT_CACHE');
27
29
  const {useCallback, useDebugValue, useState} = require('react');
@@ -31,36 +33,15 @@ const {
31
33
  getPaginationMetadata,
32
34
  } = require('relay-runtime');
33
35
 
34
- export type ReturnType<TQuery: OperationType, TKey, TFragmentData> = {
35
- data: TFragmentData,
36
- loadNext: LoadMoreFn<TQuery>,
37
- loadPrevious: LoadMoreFn<TQuery>,
38
- hasNext: boolean,
39
- hasPrevious: boolean,
40
- isLoadingNext: boolean,
41
- isLoadingPrevious: boolean,
42
- refetch: RefetchFnDynamic<TQuery, TKey>,
43
- };
44
-
45
36
  function usePaginationFragment<
46
- TQuery: OperationType,
47
- TKey: ?{+$data?: mixed, +$fragmentSpreads: FragmentType, ...},
37
+ TFragmentType: FragmentType,
38
+ TVariables: Variables,
39
+ TData,
40
+ TKey: ?{+$fragmentSpreads: TFragmentType, ...},
48
41
  >(
49
- fragmentInput: GraphQLTaggedNode,
42
+ fragmentInput: RefetchableFragment<TFragmentType, TData, TVariables>,
50
43
  parentFragmentRef: TKey,
51
- ): ReturnType<
52
- TQuery,
53
- TKey,
54
- // NOTE: This $Call ensures that the type of the returned data is either:
55
- // - nullable if the provided ref type is nullable
56
- // - non-nullable if the provided ref type is non-nullable
57
- // prettier-ignore
58
- $Call<
59
- & (<TFragmentData>( { +$data?: TFragmentData, ... }) => TFragmentData)
60
- & (<TFragmentData>(?{ +$data?: TFragmentData, ... }) => ?TFragmentData),
61
- TKey,
62
- >,
63
- > {
44
+ ): ReturnType<TVariables, TData, TKey> {
64
45
  const fragmentNode = getFragment(fragmentInput);
65
46
  useStaticFragmentNodeWarning(
66
47
  fragmentNode,
@@ -68,22 +49,18 @@ function usePaginationFragment<
68
49
  );
69
50
  const componentDisplayName = 'usePaginationFragment()';
70
51
 
71
- const {
72
- connectionPathInFragmentData,
73
- paginationRequest,
74
- paginationMetadata,
75
- identifierField,
76
- } = getPaginationMetadata(fragmentNode, componentDisplayName);
52
+ const {connectionPathInFragmentData, paginationRequest, paginationMetadata} =
53
+ getPaginationMetadata(fragmentNode, componentDisplayName);
77
54
 
78
55
  const {fragmentData, fragmentRef, refetch} = useRefetchableFragmentInternal<
79
- TQuery,
80
- TKey,
56
+ {variables: TVariables, response: TData},
57
+ {data?: TData},
81
58
  >(fragmentNode, parentFragmentRef, componentDisplayName);
82
59
  const fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef);
83
60
 
84
61
  // Backward pagination
85
62
  const [loadPrevious, hasPrevious, isLoadingPrevious, disposeFetchPrevious] =
86
- useLoadMore<TQuery>({
63
+ useLoadMore<TVariables>({
87
64
  componentDisplayName,
88
65
  connectionPathInFragmentData,
89
66
  direction: 'backward',
@@ -91,14 +68,13 @@ function usePaginationFragment<
91
68
  fragmentIdentifier,
92
69
  fragmentNode,
93
70
  fragmentRef,
94
- identifierField,
95
71
  paginationMetadata,
96
72
  paginationRequest,
97
73
  });
98
74
 
99
75
  // Forward pagination
100
76
  const [loadNext, hasNext, isLoadingNext, disposeFetchNext] =
101
- useLoadMore<TQuery>({
77
+ useLoadMore<TVariables>({
102
78
  componentDisplayName,
103
79
  connectionPathInFragmentData,
104
80
  direction: 'forward',
@@ -106,13 +82,12 @@ function usePaginationFragment<
106
82
  fragmentIdentifier,
107
83
  fragmentNode,
108
84
  fragmentRef,
109
- identifierField,
110
85
  paginationMetadata,
111
86
  paginationRequest,
112
87
  });
113
88
 
114
- const refetchPagination: RefetchFnDynamic<TQuery, TKey> = useCallback(
115
- (variables, options) => {
89
+ const refetchPagination = useCallback(
90
+ (variables: TVariables, options: void | Options) => {
116
91
  disposeFetchNext();
117
92
  disposeFetchPrevious();
118
93
  return refetch(variables, {...options, __environment: undefined});
@@ -132,6 +107,7 @@ function usePaginationFragment<
132
107
  });
133
108
  }
134
109
  return {
110
+ // $FlowFixMe[incompatible-return]
135
111
  data: fragmentData,
136
112
  loadNext,
137
113
  loadPrevious,
@@ -143,7 +119,7 @@ function usePaginationFragment<
143
119
  };
144
120
  }
145
121
 
146
- function useLoadMore<TQuery: OperationType>(
122
+ function useLoadMore<TVariables: Variables>(
147
123
  args: $Diff<
148
124
  UseLoadMoreFunctionArgs,
149
125
  {
@@ -152,15 +128,29 @@ function useLoadMore<TQuery: OperationType>(
152
128
  ...
153
129
  },
154
130
  >,
155
- ): [LoadMoreFn<TQuery>, boolean, boolean, () => void] {
156
- const [isLoadingMore, setIsLoadingMore] = useState(false);
131
+ ): [LoadMoreFn<TVariables>, boolean, boolean, () => void] {
132
+ const environment = useRelayEnvironment();
133
+ const [isLoadingMore, reallySetIsLoadingMore] = useState(false);
134
+ // Schedule this update since it must be observed by components at the same
135
+ // batch as when hasNext changes. hasNext is read from the store and store
136
+ // updates are scheduled, so this must be scheduled too.
137
+ const setIsLoadingMore = (value: boolean) => {
138
+ const schedule = environment.getScheduler()?.schedule;
139
+ if (schedule) {
140
+ schedule(() => {
141
+ reallySetIsLoadingMore(value);
142
+ });
143
+ } else {
144
+ reallySetIsLoadingMore(value);
145
+ }
146
+ };
157
147
  const observer = {
158
148
  start: () => setIsLoadingMore(true),
159
149
  complete: () => setIsLoadingMore(false),
160
150
  error: () => setIsLoadingMore(false),
161
151
  };
162
152
  const handleReset = () => setIsLoadingMore(false);
163
- const [loadMore, hasMore, disposeFetch] = useLoadMoreFunction({
153
+ const [loadMore, hasMore, disposeFetch] = useLoadMoreFunction<TVariables>({
164
154
  ...args,
165
155
  observer,
166
156
  onReset: handleReset,
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  * @flow strict-local
8
- * @emails oncall+relay
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
@@ -122,7 +122,6 @@ function usePreloadedQuery_REACT_CACHE<TQuery: OperationType>(
122
122
 
123
123
  // Read the query's root fragment -- this may suspend.
124
124
  const {fragmentNode, fragmentRef} = queryResult;
125
- // $FlowExpectedError[incompatible-return] Is this a fixable incompatible-return?
126
125
  const data = useFragmentInternal(
127
126
  fragmentNode,
128
127
  fragmentRef,
@@ -4,13 +4,14 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @emails oncall+relay
8
7
  * @flow strict-local
9
8
  * @format
9
+ * @oncall relay
10
10
  */
11
11
 
12
12
  'use strict';
13
13
 
14
+ import type {RefetchableIdentifierInfo} from '../../../relay-runtime/util/ReaderNode';
14
15
  import type {LoaderFn} from '../useQueryLoader';
15
16
  import type {
16
17
  ConcreteRequest,
@@ -98,7 +99,7 @@ type RefetchFnExact<TQuery: OperationType, TOptions = Options> = RefetchFnBase<
98
99
  type RefetchFnInexact<
99
100
  TQuery: OperationType,
100
101
  TOptions = Options,
101
- > = RefetchFnBase<$Shape<VariablesOf<TQuery>>, TOptions>;
102
+ > = RefetchFnBase<$ReadOnly<Partial<VariablesOf<TQuery>>>, TOptions>;
102
103
 
103
104
  type Action =
104
105
  | {
@@ -171,7 +172,7 @@ function useRefetchableFragmentNode<
171
172
  componentDisplayName: string,
172
173
  ): ReturnType<TQuery, TKey, InternalOptions> {
173
174
  const parentEnvironment = useRelayEnvironment();
174
- const {refetchableRequest, fragmentRefPathInResponse, identifierField} =
175
+ const {refetchableRequest, fragmentRefPathInResponse, identifierInfo} =
175
176
  getRefetchMetadata(fragmentNode, componentDisplayName);
176
177
  const fragmentIdentifier = getFragmentIdentifier(
177
178
  fragmentNode,
@@ -204,8 +205,11 @@ function useRefetchableFragmentNode<
204
205
  const shouldReset =
205
206
  environment !== mirroredEnvironment ||
206
207
  fragmentIdentifier !== mirroredFragmentIdentifier;
207
- const [queryRef, loadQuery, disposeQuery] =
208
- useQueryLoader<TQuery>(refetchableRequest);
208
+ const [queryRef, loadQuery, disposeQuery] = useQueryLoader<
209
+ TQuery['variables'],
210
+ TQuery['response'],
211
+ TQuery['rawResponse'],
212
+ >((refetchableRequest: $FlowFixMe));
209
213
 
210
214
  let fragmentRef = parentFragmentRef;
211
215
  if (shouldReset) {
@@ -233,6 +237,7 @@ function useRefetchableFragmentNode<
233
237
  debugPreviousIDAndTypename = debugFunctions.getInitialIDAndType(
234
238
  refetchQuery.request.variables,
235
239
  fragmentRefPathInResponse,
240
+ identifierInfo?.identifierQueryVariableName,
236
241
  environment,
237
242
  );
238
243
  }
@@ -340,7 +345,7 @@ function useRefetchableFragmentNode<
340
345
  fragmentIdentifier,
341
346
  fragmentNode,
342
347
  fragmentRefPathInResponse,
343
- identifierField,
348
+ identifierInfo,
344
349
  loadQuery,
345
350
  parentFragmentRef,
346
351
  refetchableRequest,
@@ -374,20 +379,23 @@ function useRefetchFunction<TQuery: OperationType>(
374
379
  fragmentIdentifier: string,
375
380
  fragmentNode: ReaderFragment,
376
381
  fragmentRefPathInResponse: $ReadOnlyArray<string | number>,
377
- identifierField: ?string,
382
+ identifierInfo: ?RefetchableIdentifierInfo,
378
383
  loadQuery: LoaderFn<TQuery>,
379
384
  parentFragmentRef: mixed,
380
385
  refetchableRequest: ConcreteRequest,
381
386
  ): RefetchFn<TQuery, InternalOptions> {
382
387
  const isMountedRef = useIsMountedRef();
383
388
  const identifierValue =
384
- identifierField != null &&
389
+ identifierInfo?.identifierField != null &&
385
390
  fragmentData != null &&
386
391
  typeof fragmentData === 'object'
387
- ? fragmentData[identifierField]
392
+ ? fragmentData[identifierInfo.identifierField]
388
393
  : null;
389
394
  return useCallback(
390
- (providedRefetchVariables, options) => {
395
+ (
396
+ providedRefetchVariables: VariablesOf<TQuery>,
397
+ options: void | InternalOptions,
398
+ ) => {
391
399
  // Bail out and warn if we're trying to refetch after the component
392
400
  // has unmounted
393
401
  if (isMountedRef.current !== true) {
@@ -448,8 +456,10 @@ function useRefetchFunction<TQuery: OperationType>(
448
456
  // If the query needs an identifier value ('id' or similar) and one
449
457
  // was not explicitly provided, read it from the fragment data.
450
458
  if (
451
- identifierField != null &&
452
- !providedRefetchVariables.hasOwnProperty('id')
459
+ identifierInfo != null &&
460
+ !providedRefetchVariables.hasOwnProperty(
461
+ identifierInfo.identifierQueryVariableName,
462
+ )
453
463
  ) {
454
464
  // @refetchable fragments are guaranteed to have an `id` selection
455
465
  // if the type is Node, implements Node, or is @fetchable. Double-check
@@ -459,11 +469,13 @@ function useRefetchFunction<TQuery: OperationType>(
459
469
  false,
460
470
  'Relay: Expected result to have a string ' +
461
471
  '`%s` in order to refetch, got `%s`.',
462
- identifierField,
472
+ identifierInfo.identifierField,
463
473
  identifierValue,
464
474
  );
465
475
  }
466
- (refetchVariables: $FlowFixMe).id = identifierValue;
476
+ (refetchVariables: $FlowFixMe)[
477
+ identifierInfo.identifierQueryVariableName
478
+ ] = identifierValue;
467
479
  }
468
480
 
469
481
  const refetchQuery = createOperationDescriptor(
@@ -512,10 +524,11 @@ if (__DEV__) {
512
524
  getInitialIDAndType(
513
525
  memoRefetchVariables: ?Variables,
514
526
  fragmentRefPathInResponse: $ReadOnlyArray<string | number>,
527
+ identifierQueryVariableName: ?string,
515
528
  environment: IEnvironment,
516
529
  ): ?DebugIDandTypename {
517
530
  const {Record} = require('relay-runtime');
518
- const id = memoRefetchVariables?.id;
531
+ const id = memoRefetchVariables?.[identifierQueryVariableName ?? 'id'];
519
532
  if (
520
533
  fragmentRefPathInResponse.length !== 1 ||
521
534
  fragmentRefPathInResponse[0] !== 'node' ||
@@ -525,7 +538,7 @@ if (__DEV__) {
525
538
  }
526
539
  const recordSource = environment.getStore().getSource();
527
540
  const record = recordSource.get(id);
528
- const typename = record && Record.getType(record);
541
+ const typename = record == null ? null : Record.getType(record);
529
542
  if (typename == null) {
530
543
  return null;
531
544
  }