react-relay 14.0.0 → 15.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 (194) hide show
  1. package/ReactRelayContainerUtils.js.flow +1 -2
  2. package/ReactRelayContext.js +1 -1
  3. package/ReactRelayContext.js.flow +1 -2
  4. package/ReactRelayFragmentContainer.js.flow +6 -4
  5. package/ReactRelayFragmentMockRenderer.js.flow +1 -2
  6. package/ReactRelayLocalQueryRenderer.js.flow +5 -5
  7. package/ReactRelayPaginationContainer.js.flow +21 -14
  8. package/ReactRelayQueryFetcher.js.flow +28 -16
  9. package/ReactRelayQueryRenderer.js.flow +42 -13
  10. package/ReactRelayQueryRendererContext.js.flow +2 -3
  11. package/ReactRelayRefetchContainer.js.flow +9 -9
  12. package/ReactRelayTestMocker.js.flow +3 -3
  13. package/ReactRelayTypes.js.flow +7 -8
  14. package/RelayContext.js.flow +1 -2
  15. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +4 -5
  16. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +4 -5
  17. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +4 -5
  18. package/__flowtests__/RelayModern-flowtest.js.flow +3 -4
  19. package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +3 -4
  20. package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +3 -4
  21. package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +3 -4
  22. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +3 -4
  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 -2
  34. package/buildReactRelayContainer.js.flow +7 -7
  35. package/getRootVariablesForFragments.js.flow +1 -3
  36. package/hooks.js +1 -1
  37. package/hooks.js.flow +4 -2
  38. package/index.js +1 -1
  39. package/index.js.flow +6 -2
  40. package/isRelayEnvironment.js.flow +1 -2
  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 -2
  46. package/lib/ReactRelayContainerUtils.js +2 -3
  47. package/lib/ReactRelayContext.js +3 -4
  48. package/lib/ReactRelayFragmentContainer.js +47 -73
  49. package/lib/ReactRelayFragmentMockRenderer.js +2 -4
  50. package/lib/ReactRelayLocalQueryRenderer.js +18 -31
  51. package/lib/ReactRelayPaginationContainer.js +74 -164
  52. package/lib/ReactRelayQueryFetcher.js +49 -76
  53. package/lib/ReactRelayQueryRenderer.js +63 -84
  54. package/lib/ReactRelayQueryRendererContext.js +2 -2
  55. package/lib/ReactRelayRefetchContainer.js +58 -108
  56. package/lib/ReactRelayTestMocker.js +33 -68
  57. package/lib/ReactRelayTypes.js +2 -1
  58. package/lib/RelayContext.js +4 -7
  59. package/lib/assertFragmentMap.js +3 -5
  60. package/lib/buildReactRelayContainer.js +11 -27
  61. package/lib/getRootVariablesForFragments.js +6 -10
  62. package/lib/hooks.js +5 -18
  63. package/lib/index.js +7 -24
  64. package/lib/isRelayEnvironment.js +5 -4
  65. package/lib/jest-react/enqueueTask.js +5 -9
  66. package/lib/jest-react/index.js +0 -1
  67. package/lib/jest-react/internalAct.js +9 -20
  68. package/lib/legacy.js +2 -8
  69. package/lib/multi-actor/ActorChange.js +2 -5
  70. package/lib/multi-actor/index.js +2 -1
  71. package/lib/multi-actor/useRelayActorEnvironment.js +4 -8
  72. package/lib/relay-hooks/EntryPointContainer.react.js +9 -15
  73. package/lib/relay-hooks/EntryPointTypes.flow.js +5 -3
  74. package/lib/relay-hooks/FragmentResource.js +109 -203
  75. package/lib/relay-hooks/HooksImplementation.js +3 -6
  76. package/lib/relay-hooks/InternalLogger.js +2 -3
  77. package/lib/relay-hooks/LRUCache.js +2 -20
  78. package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +33 -54
  79. package/lib/relay-hooks/MatchContainer.js +15 -24
  80. package/lib/relay-hooks/ProfilerContext.js +3 -3
  81. package/lib/relay-hooks/QueryResource.js +31 -101
  82. package/lib/relay-hooks/RelayEnvironmentProvider.js +5 -9
  83. package/lib/relay-hooks/SuspenseResource.js +9 -33
  84. package/lib/relay-hooks/loadEntryPoint.js +19 -31
  85. package/lib/relay-hooks/loadQuery.js +42 -78
  86. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +11 -37
  87. package/lib/relay-hooks/prepareEntryPoint_DEPRECATED.js +9 -15
  88. package/lib/relay-hooks/react-cache/RelayReactCache.js +7 -12
  89. package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +27 -81
  90. package/lib/relay-hooks/react-cache/readFragmentInternal_REACT_CACHE.js +206 -0
  91. package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +195 -215
  92. package/lib/relay-hooks/react-cache/useFragment_REACT_CACHE.js +5 -15
  93. package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +17 -24
  94. package/lib/relay-hooks/react-cache/usePaginationFragment_REACT_CACHE.js +149 -0
  95. package/lib/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js +24 -39
  96. package/lib/relay-hooks/react-cache/useRefetchableFragmentInternal_REACT_CACHE.js +325 -0
  97. package/lib/relay-hooks/react-cache/useRefetchableFragment_REACT_CACHE.js +37 -0
  98. package/lib/relay-hooks/useBlockingPaginationFragment.js +73 -93
  99. package/lib/relay-hooks/useClientQuery.js +30 -0
  100. package/lib/relay-hooks/useEntryPointLoader.js +18 -38
  101. package/lib/relay-hooks/useFetchTrackingRef.js +10 -12
  102. package/lib/relay-hooks/useFragment.js +8 -19
  103. package/lib/relay-hooks/useFragmentNode.js +20 -32
  104. package/lib/relay-hooks/useIsMountedRef.js +4 -6
  105. package/lib/relay-hooks/useIsOperationNodeActive.js +8 -20
  106. package/lib/relay-hooks/useIsParentQueryActive.js +3 -6
  107. package/lib/relay-hooks/useLazyLoadQuery.js +7 -24
  108. package/lib/relay-hooks/useLazyLoadQueryNode.js +23 -34
  109. package/lib/relay-hooks/useLoadMoreFunction.js +46 -78
  110. package/lib/relay-hooks/useMemoOperationDescriptor.js +6 -15
  111. package/lib/relay-hooks/useMemoVariables.js +15 -34
  112. package/lib/relay-hooks/useMutation.js +9 -27
  113. package/lib/relay-hooks/usePaginationFragment.js +73 -76
  114. package/lib/relay-hooks/usePreloadedQuery.js +13 -44
  115. package/lib/relay-hooks/useQueryLoader.js +24 -49
  116. package/lib/relay-hooks/useRefetchableFragment.js +19 -17
  117. package/lib/relay-hooks/useRefetchableFragmentNode.js +65 -109
  118. package/lib/relay-hooks/useRelayEnvironment.js +4 -8
  119. package/lib/relay-hooks/useStaticFragmentNodeWarning.js +4 -8
  120. package/lib/relay-hooks/useSubscribeToInvalidationState.js +8 -9
  121. package/lib/relay-hooks/useSubscription.js +5 -10
  122. package/lib/relay-hooks/useUnsafeRef_DEPRECATED.js +29 -0
  123. package/multi-actor/ActorChange.js.flow +1 -1
  124. package/multi-actor/index.js.flow +1 -1
  125. package/multi-actor/useRelayActorEnvironment.js.flow +2 -4
  126. package/package.json +2 -2
  127. package/react-relay-hooks.js +2 -2
  128. package/react-relay-hooks.min.js +2 -2
  129. package/react-relay-legacy.js +2 -2
  130. package/react-relay-legacy.min.js +2 -2
  131. package/react-relay.js +2 -2
  132. package/react-relay.min.js +2 -2
  133. package/relay-hooks/EntryPointContainer.react.js.flow +3 -5
  134. package/relay-hooks/EntryPointTypes.flow.js.flow +37 -37
  135. package/relay-hooks/FragmentResource.js.flow +43 -19
  136. package/relay-hooks/HooksImplementation.js.flow +7 -9
  137. package/relay-hooks/InternalLogger.js.flow +1 -3
  138. package/relay-hooks/LRUCache.js.flow +1 -3
  139. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +19 -14
  140. package/relay-hooks/MatchContainer.js.flow +6 -8
  141. package/relay-hooks/ProfilerContext.js.flow +1 -3
  142. package/relay-hooks/QueryResource.js.flow +29 -11
  143. package/relay-hooks/RelayEnvironmentProvider.js.flow +4 -6
  144. package/relay-hooks/SuspenseResource.js.flow +1 -3
  145. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +6 -4
  146. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +4 -4
  147. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +3 -1
  148. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +3 -1
  149. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +39 -39
  150. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +1 -3
  151. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +37 -38
  152. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +18 -20
  153. package/relay-hooks/__flowtests__/utils.js.flow +21 -12
  154. package/relay-hooks/loadEntryPoint.js.flow +11 -6
  155. package/relay-hooks/loadQuery.js.flow +11 -7
  156. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +9 -12
  157. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +13 -10
  158. package/relay-hooks/react-cache/RelayReactCache.js.flow +1 -3
  159. package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +26 -20
  160. package/relay-hooks/react-cache/readFragmentInternal_REACT_CACHE.js.flow +297 -0
  161. package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +136 -96
  162. package/relay-hooks/react-cache/useFragment_REACT_CACHE.js.flow +1 -3
  163. package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +3 -5
  164. package/relay-hooks/react-cache/usePaginationFragment_REACT_CACHE.js.flow +190 -0
  165. package/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js.flow +3 -6
  166. package/relay-hooks/react-cache/useRefetchableFragmentInternal_REACT_CACHE.js.flow +601 -0
  167. package/relay-hooks/react-cache/useRefetchableFragment_REACT_CACHE.js.flow +65 -0
  168. package/relay-hooks/useBlockingPaginationFragment.js.flow +86 -59
  169. package/relay-hooks/useClientQuery.js.flow +39 -0
  170. package/relay-hooks/useEntryPointLoader.js.flow +16 -14
  171. package/relay-hooks/useFetchTrackingRef.js.flow +7 -8
  172. package/relay-hooks/useFragment.js.flow +2 -4
  173. package/relay-hooks/useFragmentNode.js.flow +7 -8
  174. package/relay-hooks/useIsMountedRef.js.flow +2 -4
  175. package/relay-hooks/useIsOperationNodeActive.js.flow +1 -1
  176. package/relay-hooks/useIsParentQueryActive.js.flow +1 -1
  177. package/relay-hooks/useLazyLoadQuery.js.flow +9 -32
  178. package/relay-hooks/useLazyLoadQueryNode.js.flow +4 -6
  179. package/relay-hooks/useLoadMoreFunction.js.flow +20 -17
  180. package/relay-hooks/useMemoOperationDescriptor.js.flow +3 -5
  181. package/relay-hooks/useMemoVariables.js.flow +13 -31
  182. package/relay-hooks/useMutation.js.flow +6 -8
  183. package/relay-hooks/usePaginationFragment.js.flow +75 -43
  184. package/relay-hooks/usePreloadedQuery.js.flow +49 -43
  185. package/relay-hooks/useQueryLoader.js.flow +89 -28
  186. package/relay-hooks/useRefetchableFragment.js.flow +83 -23
  187. package/relay-hooks/useRefetchableFragmentNode.js.flow +26 -22
  188. package/relay-hooks/useRelayEnvironment.js.flow +2 -4
  189. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +3 -5
  190. package/relay-hooks/useSubscribeToInvalidationState.js.flow +2 -4
  191. package/relay-hooks/useSubscription.js.flow +1 -3
  192. package/relay-hooks/useUnsafeRef_DEPRECATED.js.flow +25 -0
  193. package/lib/readContext.js +0 -28
  194. package/readContext.js.flow +0 -31
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall relay
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {LoaderFn} from '../useQueryLoader';
15
+ import type {
16
+ ConcreteRequest,
17
+ Disposable,
18
+ FetchPolicy,
19
+ IEnvironment,
20
+ OperationDescriptor,
21
+ OperationType,
22
+ ReaderFragment,
23
+ RenderPolicy,
24
+ Variables,
25
+ VariablesOf,
26
+ } from 'relay-runtime';
27
+
28
+ const ProfilerContext = require('../ProfilerContext');
29
+ const {getQueryResourceForEnvironment} = require('../QueryResource');
30
+ const useIsMountedRef = require('../useIsMountedRef');
31
+ const useQueryLoader = require('../useQueryLoader');
32
+ const useRelayEnvironment = require('../useRelayEnvironment');
33
+ const readFragmentInternal = require('./readFragmentInternal_REACT_CACHE');
34
+ const useFragmentInternal = require('./useFragmentInternal_REACT_CACHE');
35
+ const invariant = require('invariant');
36
+ const {useCallback, useContext, useReducer} = require('react');
37
+ const {
38
+ __internal: {fetchQuery},
39
+ createOperationDescriptor,
40
+ getFragmentIdentifier,
41
+ getRefetchMetadata,
42
+ getSelector,
43
+ getValueAtPath,
44
+ } = require('relay-runtime');
45
+ const warning = require('warning');
46
+
47
+ export type RefetchFn<
48
+ TQuery: OperationType,
49
+ TOptions = Options,
50
+ > = RefetchFnExact<TQuery, TOptions>;
51
+
52
+ // NOTE: RefetchFnDynamic returns a refetch function that:
53
+ // - Expects the /exact/ set of query variables if the provided key type is
54
+ // /nullable/.
55
+ // - Or, expects /a subset/ of the query variables if the provided key type is
56
+ // /non-null/.
57
+ // prettier-ignore
58
+ export type RefetchFnDynamic<
59
+ TQuery: OperationType,
60
+ TKey: ?{ +$data?: mixed, ... },
61
+ TOptions = Options,
62
+ > = $Call<
63
+ & (( { +$data?: mixed, ... }) => RefetchFnInexact<TQuery, TOptions>)
64
+ & ((?{ +$data?: mixed, ... }) => RefetchFnExact<TQuery, TOptions>),
65
+ TKey
66
+ >;
67
+
68
+ export type ReturnType<
69
+ TQuery: OperationType,
70
+ TKey: ?{+$data?: mixed, ...},
71
+ TOptions = Options,
72
+ > = {
73
+ fragmentData: mixed,
74
+ fragmentRef: mixed,
75
+ refetch: RefetchFnDynamic<TQuery, TKey, TOptions>,
76
+ };
77
+
78
+ export type Options = {
79
+ fetchPolicy?: FetchPolicy,
80
+ onComplete?: (Error | null) => void,
81
+ UNSTABLE_renderPolicy?: RenderPolicy,
82
+ };
83
+
84
+ type InternalOptions = {
85
+ ...Options,
86
+ __environment?: IEnvironment,
87
+ };
88
+
89
+ type RefetchFnBase<TVars, TOptions> = (
90
+ vars: TVars,
91
+ options?: TOptions,
92
+ ) => Disposable;
93
+
94
+ type RefetchFnExact<TQuery: OperationType, TOptions = Options> = RefetchFnBase<
95
+ VariablesOf<TQuery>,
96
+ TOptions,
97
+ >;
98
+ type RefetchFnInexact<
99
+ TQuery: OperationType,
100
+ TOptions = Options,
101
+ > = RefetchFnBase<Partial<VariablesOf<TQuery>>, TOptions>;
102
+
103
+ type Action =
104
+ | {
105
+ type: 'reset',
106
+ environment: IEnvironment,
107
+ fragmentIdentifier: string,
108
+ }
109
+ | {
110
+ type: 'refetch',
111
+ refetchQuery: OperationDescriptor,
112
+ fetchPolicy?: FetchPolicy,
113
+ renderPolicy?: RenderPolicy,
114
+ onComplete?: (Error | null) => void,
115
+ refetchEnvironment: ?IEnvironment,
116
+ };
117
+
118
+ type RefetchState = {
119
+ fetchPolicy: FetchPolicy | void,
120
+ mirroredEnvironment: IEnvironment,
121
+ mirroredFragmentIdentifier: string,
122
+ onComplete: ((Error | null) => void) | void,
123
+ refetchEnvironment?: ?IEnvironment,
124
+ refetchQuery: OperationDescriptor | null,
125
+ renderPolicy: RenderPolicy | void,
126
+ };
127
+
128
+ type DebugIDandTypename = {
129
+ id: string,
130
+ typename: string,
131
+ ...
132
+ };
133
+
134
+ function reducer(state: RefetchState, action: Action): RefetchState {
135
+ switch (action.type) {
136
+ case 'refetch': {
137
+ return {
138
+ ...state,
139
+ fetchPolicy: action.fetchPolicy,
140
+ mirroredEnvironment:
141
+ action.refetchEnvironment ?? state.mirroredEnvironment,
142
+ onComplete: action.onComplete,
143
+ refetchEnvironment: action.refetchEnvironment,
144
+ refetchQuery: action.refetchQuery,
145
+ renderPolicy: action.renderPolicy,
146
+ };
147
+ }
148
+ case 'reset': {
149
+ return {
150
+ fetchPolicy: undefined,
151
+ mirroredEnvironment: action.environment,
152
+ mirroredFragmentIdentifier: action.fragmentIdentifier,
153
+ onComplete: undefined,
154
+ refetchQuery: null,
155
+ renderPolicy: undefined,
156
+ };
157
+ }
158
+ default: {
159
+ (action.type: empty);
160
+ throw new Error('useRefetchableFragmentNode: Unexpected action type');
161
+ }
162
+ }
163
+ }
164
+
165
+ function useRefetchableFragmentNode<
166
+ TQuery: OperationType,
167
+ TKey: ?{+$data?: mixed, ...},
168
+ >(
169
+ fragmentNode: ReaderFragment,
170
+ parentFragmentRef: mixed,
171
+ componentDisplayName: string,
172
+ ): ReturnType<TQuery, TKey, InternalOptions> {
173
+ const parentEnvironment = useRelayEnvironment();
174
+ const {refetchableRequest, fragmentRefPathInResponse, identifierField} =
175
+ getRefetchMetadata(fragmentNode, componentDisplayName);
176
+ const fragmentIdentifier = getFragmentIdentifier(
177
+ fragmentNode,
178
+ parentFragmentRef,
179
+ );
180
+
181
+ const [refetchState, dispatch] = useReducer(reducer, {
182
+ fetchPolicy: undefined,
183
+ mirroredEnvironment: parentEnvironment,
184
+ mirroredFragmentIdentifier: fragmentIdentifier,
185
+ onComplete: undefined,
186
+ refetchEnvironment: null,
187
+ refetchQuery: null,
188
+ renderPolicy: undefined,
189
+ });
190
+ const {
191
+ fetchPolicy,
192
+ mirroredEnvironment,
193
+ mirroredFragmentIdentifier,
194
+ onComplete,
195
+ refetchEnvironment,
196
+ refetchQuery,
197
+ renderPolicy,
198
+ } = refetchState;
199
+ const environment = refetchEnvironment ?? parentEnvironment;
200
+
201
+ const QueryResource = getQueryResourceForEnvironment(environment);
202
+ const profilerContext = useContext(ProfilerContext);
203
+
204
+ const shouldReset =
205
+ environment !== mirroredEnvironment ||
206
+ fragmentIdentifier !== mirroredFragmentIdentifier;
207
+ const [queryRef, loadQuery, disposeQuery] = useQueryLoader<
208
+ TQuery['variables'],
209
+ TQuery['response'],
210
+ TQuery['rawResponse'],
211
+ >((refetchableRequest: $FlowFixMe));
212
+
213
+ let fragmentRef = parentFragmentRef;
214
+ if (shouldReset) {
215
+ dispatch({
216
+ type: 'reset',
217
+ environment,
218
+ fragmentIdentifier,
219
+ });
220
+ disposeQuery();
221
+ } else if (refetchQuery != null && queryRef != null) {
222
+ // If refetch was called, we expect to have a refetchQuery and queryRef
223
+ // in state, since both state updates to set the refetchQuery and the
224
+ // queryRef occur simultaneously.
225
+ // In this case, we need to read the refetched query data (potentially
226
+ // suspending if it's in flight), and extract the new fragment ref
227
+ // from the query in order read the current @refetchable fragment
228
+ // with the updated fragment owner as the new refetchQuery.
229
+
230
+ // Before observing the refetch, record the current ID and typename
231
+ // so that, if we are refetching existing data on
232
+ // a field that implements Node, after refetching we
233
+ // can validate that the received data is consistent
234
+ let debugPreviousIDAndTypename: ?DebugIDandTypename;
235
+ if (__DEV__) {
236
+ debugPreviousIDAndTypename = debugFunctions.getInitialIDAndType(
237
+ refetchQuery.request.variables,
238
+ fragmentRefPathInResponse,
239
+ environment,
240
+ );
241
+ }
242
+
243
+ const handleQueryCompleted = (maybeError: void | Error) => {
244
+ onComplete && onComplete(maybeError ?? null);
245
+ };
246
+
247
+ // The queryRef.source obtained from useQueryLoader will be
248
+ // an observable we can consume /if/ a network request was
249
+ // started. Otherwise, given that QueryResource.prepare
250
+ // always expects an observable we fall back to a new network
251
+ // observable. Note however that if loadQuery did not make a network
252
+ // request, we don't expect to make one here, unless the state of
253
+ // the cache has changed between the call to refetch and this
254
+ // render.
255
+ const fetchObservable =
256
+ queryRef.source != null
257
+ ? queryRef.source
258
+ : fetchQuery(environment, refetchQuery);
259
+
260
+ // Now wwe can we read the refetch query here using the
261
+ // queryRef provided from useQueryLoader. Note that the
262
+ // network request is started during the call to refetch,
263
+ // but if the refetch query is still in flight, we will suspend
264
+ // at this point:
265
+ const queryResult = profilerContext.wrapPrepareQueryResource(() => {
266
+ return QueryResource.prepare(
267
+ refetchQuery,
268
+ fetchObservable,
269
+ fetchPolicy,
270
+ renderPolicy,
271
+ {
272
+ error: handleQueryCompleted,
273
+ complete: () => {
274
+ // Validate that the type of the object we got back matches the type
275
+ // of the object already in the store
276
+ if (__DEV__) {
277
+ debugFunctions.checkSameTypeAfterRefetch(
278
+ debugPreviousIDAndTypename,
279
+ environment,
280
+ fragmentNode,
281
+ componentDisplayName,
282
+ );
283
+ }
284
+ handleQueryCompleted();
285
+ },
286
+ },
287
+ queryRef.fetchKey,
288
+ profilerContext,
289
+ );
290
+ });
291
+
292
+ const queryData = readFragmentInternal(
293
+ environment,
294
+ queryResult.fragmentNode,
295
+ queryResult.fragmentRef,
296
+ componentDisplayName,
297
+ ).data;
298
+ invariant(
299
+ queryData != null,
300
+ 'Relay: Expected to be able to read refetch query response. ' +
301
+ "If you're seeing this, this is likely a bug in Relay.",
302
+ );
303
+
304
+ // After reading/fetching the refetch query, we extract from the
305
+ // refetch query response the new fragment ref we need to use to read
306
+ // the fragment. The new fragment ref will point to the refetch query
307
+ // as its fragment owner.
308
+ const refetchedFragmentRef = getValueAtPath(
309
+ queryData,
310
+ fragmentRefPathInResponse,
311
+ );
312
+ fragmentRef = refetchedFragmentRef;
313
+
314
+ if (__DEV__) {
315
+ // Validate that the id of the object we got back matches the id
316
+ // we queried for in the variables.
317
+ // We do this during render instead of onComplete to make sure we are
318
+ // only validating the most recent refetch.
319
+ debugFunctions.checkSameIDAfterRefetch(
320
+ debugPreviousIDAndTypename,
321
+ fragmentRef,
322
+ fragmentNode,
323
+ componentDisplayName,
324
+ );
325
+ }
326
+ }
327
+
328
+ // We read and subscribe to the fragment using useFragmentNode.
329
+ // If refetch was called, we read the fragment using the new computed
330
+ // fragment ref from the refetch query response; otherwise, we use the
331
+ // fragment ref passed by the caller as normal.
332
+ const fragmentData = useFragmentInternal(
333
+ fragmentNode,
334
+ fragmentRef,
335
+ componentDisplayName,
336
+ );
337
+
338
+ const refetch = useRefetchFunction<TQuery>(
339
+ componentDisplayName,
340
+ dispatch,
341
+ disposeQuery,
342
+ fragmentData,
343
+ fragmentIdentifier,
344
+ fragmentNode,
345
+ fragmentRefPathInResponse,
346
+ identifierField,
347
+ loadQuery,
348
+ parentFragmentRef,
349
+ refetchableRequest,
350
+ );
351
+ return {
352
+ fragmentData,
353
+ fragmentRef,
354
+ refetch,
355
+ };
356
+ }
357
+
358
+ function useRefetchFunction<TQuery: OperationType>(
359
+ componentDisplayName: string,
360
+ dispatch: (
361
+ | {
362
+ environment: IEnvironment,
363
+ fragmentIdentifier: string,
364
+ type: 'reset',
365
+ }
366
+ | {
367
+ fetchPolicy?: FetchPolicy,
368
+ onComplete?: (Error | null) => void,
369
+ refetchEnvironment: ?IEnvironment,
370
+ refetchQuery: OperationDescriptor,
371
+ renderPolicy?: RenderPolicy,
372
+ type: 'refetch',
373
+ },
374
+ ) => void,
375
+ disposeQuery: () => void,
376
+ fragmentData: mixed,
377
+ fragmentIdentifier: string,
378
+ fragmentNode: ReaderFragment,
379
+ fragmentRefPathInResponse: $ReadOnlyArray<string | number>,
380
+ identifierField: ?string,
381
+ loadQuery: LoaderFn<TQuery>,
382
+ parentFragmentRef: mixed,
383
+ refetchableRequest: ConcreteRequest,
384
+ ): RefetchFn<TQuery, InternalOptions> {
385
+ const isMountedRef = useIsMountedRef();
386
+ const identifierValue =
387
+ identifierField != null &&
388
+ fragmentData != null &&
389
+ typeof fragmentData === 'object'
390
+ ? fragmentData[identifierField]
391
+ : null;
392
+ return useCallback(
393
+ (
394
+ providedRefetchVariables: VariablesOf<TQuery>,
395
+ options: void | InternalOptions,
396
+ ) => {
397
+ // Bail out and warn if we're trying to refetch after the component
398
+ // has unmounted
399
+ if (isMountedRef.current !== true) {
400
+ warning(
401
+ false,
402
+ 'Relay: Unexpected call to `refetch` on unmounted component for fragment ' +
403
+ '`%s` in `%s`. It looks like some instances of your component are ' +
404
+ 'still trying to fetch data but they already unmounted. ' +
405
+ 'Please make sure you clear all timers, intervals, ' +
406
+ 'async calls, etc that may trigger a fetch.',
407
+ fragmentNode.name,
408
+ componentDisplayName,
409
+ );
410
+ return {dispose: () => {}};
411
+ }
412
+ if (parentFragmentRef == null) {
413
+ warning(
414
+ false,
415
+ 'Relay: Unexpected call to `refetch` while using a null fragment ref ' +
416
+ 'for fragment `%s` in `%s`. When calling `refetch`, we expect ' +
417
+ "initial fragment data to be non-null. Please make sure you're " +
418
+ 'passing a valid fragment ref to `%s` before calling ' +
419
+ '`refetch`, or make sure you pass all required variables to `refetch`.',
420
+ fragmentNode.name,
421
+ componentDisplayName,
422
+ componentDisplayName,
423
+ );
424
+ }
425
+
426
+ const refetchEnvironment = options?.__environment;
427
+ const fetchPolicy = options?.fetchPolicy;
428
+ const renderPolicy = options?.UNSTABLE_renderPolicy;
429
+ const onComplete = options?.onComplete;
430
+ const fragmentSelector = getSelector(fragmentNode, parentFragmentRef);
431
+ let parentVariables: Variables;
432
+ let fragmentVariables: Variables;
433
+ if (fragmentSelector == null) {
434
+ parentVariables = {};
435
+ fragmentVariables = {};
436
+ } else if (fragmentSelector.kind === 'PluralReaderSelector') {
437
+ parentVariables = fragmentSelector.selectors[0]?.owner.variables ?? {};
438
+ fragmentVariables = fragmentSelector.selectors[0]?.variables ?? {};
439
+ } else {
440
+ parentVariables = fragmentSelector.owner.variables;
441
+ fragmentVariables = fragmentSelector.variables;
442
+ }
443
+
444
+ // A user of `useRefetchableFragment()` may pass a subset of
445
+ // all variables required by the fragment when calling `refetch()`.
446
+ // We fill in any variables not passed by the call to `refetch()` with the
447
+ // variables from the original parent fragment owner.
448
+ const refetchVariables: VariablesOf<TQuery> = {
449
+ ...(parentVariables: $FlowFixMe),
450
+ ...fragmentVariables,
451
+ ...providedRefetchVariables,
452
+ };
453
+
454
+ // If the query needs an identifier value ('id' or similar) and one
455
+ // was not explicitly provided, read it from the fragment data.
456
+ if (
457
+ identifierField != null &&
458
+ !providedRefetchVariables.hasOwnProperty('id')
459
+ ) {
460
+ // @refetchable fragments are guaranteed to have an `id` selection
461
+ // if the type is Node, implements Node, or is @fetchable. Double-check
462
+ // that there actually is a value at runtime.
463
+ if (typeof identifierValue !== 'string') {
464
+ warning(
465
+ false,
466
+ 'Relay: Expected result to have a string ' +
467
+ '`%s` in order to refetch, got `%s`.',
468
+ identifierField,
469
+ identifierValue,
470
+ );
471
+ }
472
+ (refetchVariables: $FlowFixMe).id = identifierValue;
473
+ }
474
+
475
+ const refetchQuery = createOperationDescriptor(
476
+ refetchableRequest,
477
+ refetchVariables,
478
+ {force: true},
479
+ );
480
+
481
+ // We call loadQuery which will start a network request if necessary
482
+ // and update the querRef from useQueryLoader.
483
+ // Note the following:
484
+ // - loadQuery will dispose of any previously refetched queries.
485
+ // - We use the variables extracted off the OperationDescriptor
486
+ // so that they have been filtered out to include only the
487
+ // variables actually declared in the query.
488
+ loadQuery(refetchQuery.request.variables, {
489
+ fetchPolicy,
490
+ __environment: refetchEnvironment,
491
+ __nameForWarning: 'refetch',
492
+ });
493
+
494
+ dispatch({
495
+ type: 'refetch',
496
+ fetchPolicy,
497
+ onComplete,
498
+ refetchEnvironment,
499
+ refetchQuery,
500
+ renderPolicy,
501
+ });
502
+ return {dispose: disposeQuery};
503
+ },
504
+ // NOTE: We disable react-hooks-deps warning because:
505
+ // - We know fragmentRefPathInResponse is static, so it can be omitted from
506
+ // deps
507
+ // - We know fragmentNode is static, so it can be omitted from deps.
508
+ // - fragmentNode and parentFragmentRef are also captured by including
509
+ // fragmentIdentifier
510
+ // eslint-disable-next-line react-hooks/exhaustive-deps
511
+ [fragmentIdentifier, dispatch, disposeQuery, identifierValue, loadQuery],
512
+ );
513
+ }
514
+
515
+ let debugFunctions;
516
+ if (__DEV__) {
517
+ debugFunctions = {
518
+ getInitialIDAndType(
519
+ memoRefetchVariables: ?Variables,
520
+ fragmentRefPathInResponse: $ReadOnlyArray<string | number>,
521
+ environment: IEnvironment,
522
+ ): ?DebugIDandTypename {
523
+ const {Record} = require('relay-runtime');
524
+ const id = memoRefetchVariables?.id;
525
+ if (
526
+ fragmentRefPathInResponse.length !== 1 ||
527
+ fragmentRefPathInResponse[0] !== 'node' ||
528
+ id == null
529
+ ) {
530
+ return null;
531
+ }
532
+ const recordSource = environment.getStore().getSource();
533
+ const record = recordSource.get(id);
534
+ const typename = record && Record.getType(record);
535
+ if (typename == null) {
536
+ return null;
537
+ }
538
+ return {
539
+ id,
540
+ typename,
541
+ };
542
+ },
543
+
544
+ checkSameTypeAfterRefetch(
545
+ previousIDAndType: ?DebugIDandTypename,
546
+ environment: IEnvironment,
547
+ fragmentNode: ReaderFragment,
548
+ componentDisplayName: string,
549
+ ): void {
550
+ const {Record} = require('relay-runtime');
551
+ if (!previousIDAndType) {
552
+ return;
553
+ }
554
+ const recordSource = environment.getStore().getSource();
555
+ const record = recordSource.get(previousIDAndType.id);
556
+ const typename = record && Record.getType(record);
557
+ if (typename !== previousIDAndType.typename) {
558
+ warning(
559
+ false,
560
+ 'Relay: Call to `refetch` returned data with a different ' +
561
+ '__typename: was `%s`, now `%s`, on `%s` in `%s`. ' +
562
+ 'Please make sure the server correctly implements' +
563
+ 'unique id requirement.',
564
+ previousIDAndType.typename,
565
+ typename,
566
+ fragmentNode.name,
567
+ componentDisplayName,
568
+ );
569
+ }
570
+ },
571
+
572
+ checkSameIDAfterRefetch(
573
+ previousIDAndTypename: ?DebugIDandTypename,
574
+ refetchedFragmentRef: mixed,
575
+ fragmentNode: ReaderFragment,
576
+ componentDisplayName: string,
577
+ ): void {
578
+ if (previousIDAndTypename == null) {
579
+ return;
580
+ }
581
+ const {ID_KEY} = require('relay-runtime');
582
+ // $FlowExpectedError[incompatible-use]
583
+ const resultID = refetchedFragmentRef[ID_KEY];
584
+ if (resultID != null && resultID !== previousIDAndTypename.id) {
585
+ warning(
586
+ false,
587
+ 'Relay: Call to `refetch` returned a different id, expected ' +
588
+ '`%s`, got `%s`, on `%s` in `%s`. ' +
589
+ 'Please make sure the server correctly implements ' +
590
+ 'unique id requirement.',
591
+ resultID,
592
+ previousIDAndTypename.id,
593
+ fragmentNode.name,
594
+ componentDisplayName,
595
+ );
596
+ }
597
+ },
598
+ };
599
+ }
600
+
601
+ module.exports = useRefetchableFragmentNode;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall relay
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {RefetchFnDynamic} from './useRefetchableFragmentInternal_REACT_CACHE';
15
+ import type {
16
+ FragmentType,
17
+ GraphQLTaggedNode,
18
+ OperationType,
19
+ } from 'relay-runtime';
20
+
21
+ const useStaticFragmentNodeWarning = require('../useStaticFragmentNodeWarning');
22
+ const useRefetchableFragmentInternal = require('./useRefetchableFragmentInternal_REACT_CACHE');
23
+ const {useDebugValue} = require('react');
24
+ const {getFragment} = require('relay-runtime');
25
+
26
+ type ReturnType<TQuery: OperationType, TKey: ?{+$data?: mixed, ...}> = [
27
+ // NOTE: This $Call ensures that the type of the returned data is either:
28
+ // - nullable if the provided ref type is nullable
29
+ // - non-nullable if the provided ref type is non-nullable
30
+ // prettier-ignore
31
+ $Call<
32
+ & (<TFragmentData>( { +$data?: TFragmentData, ... }) => TFragmentData)
33
+ & (<TFragmentData>(?{ +$data?: TFragmentData, ... }) => ?TFragmentData),
34
+ TKey,
35
+ >,
36
+ RefetchFnDynamic<TQuery, TKey>,
37
+ ];
38
+
39
+ function useRefetchableFragment<
40
+ TQuery: OperationType,
41
+ TKey: ?{+$data?: mixed, +$fragmentSpreads: FragmentType, ...},
42
+ >(
43
+ fragmentInput: GraphQLTaggedNode,
44
+ fragmentRef: TKey,
45
+ ): ReturnType<TQuery, TKey> {
46
+ const fragmentNode = getFragment(fragmentInput);
47
+ useStaticFragmentNodeWarning(
48
+ fragmentNode,
49
+ 'first argument of useRefetchableFragment()',
50
+ );
51
+ const {fragmentData, refetch} = useRefetchableFragmentInternal<TQuery, TKey>(
52
+ fragmentNode,
53
+ fragmentRef,
54
+ 'useRefetchableFragment()',
55
+ );
56
+ if (__DEV__) {
57
+ // eslint-disable-next-line react-hooks/rules-of-hooks
58
+ useDebugValue({fragment: fragmentNode.name, data: fragmentData});
59
+ }
60
+ /* $FlowExpectedError[prop-missing] : Exposed options is a subset of internal
61
+ * options */
62
+ return [fragmentData, (refetch: RefetchFnDynamic<TQuery, TKey>)];
63
+ }
64
+
65
+ module.exports = useRefetchableFragment;