react-relay 18.2.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/ReactRelayContainerUtils.js.flow +2 -2
  2. package/ReactRelayContext.js +1 -1
  3. package/ReactRelayFragmentContainer.js.flow +5 -7
  4. package/ReactRelayLoggingContext.js.flow +21 -0
  5. package/ReactRelayPaginationContainer.js.flow +8 -8
  6. package/ReactRelayRefetchContainer.js.flow +8 -8
  7. package/ReactRelayTypes.js.flow +18 -11
  8. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +1 -8
  9. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +2 -5
  10. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +2 -5
  11. package/buildReactRelayContainer.js.flow +4 -5
  12. package/hooks.js +1 -1
  13. package/index.js +1 -1
  14. package/index.js.flow +3 -3
  15. package/legacy.js +1 -1
  16. package/lib/ReactRelayLoggingContext.js +6 -0
  17. package/lib/index.js +2 -2
  18. package/lib/relay-hooks/legacy/FragmentResource.js +3 -3
  19. package/lib/relay-hooks/legacy/useRefetchableFragmentNode.js +1 -1
  20. package/lib/relay-hooks/loadEntryPoint.js +3 -0
  21. package/lib/relay-hooks/loadQuery.js +5 -3
  22. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +7 -2
  23. package/lib/relay-hooks/readFragmentInternal.js +3 -3
  24. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +12 -7
  25. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +25 -10
  26. package/lib/relay-hooks/useLoadMoreFunction.js +3 -2
  27. package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +3 -2
  28. package/lib/relay-hooks/usePaginationFragment.js +5 -1
  29. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment.js +228 -0
  30. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +8 -8
  31. package/lib/relay-hooks/useRefetchableFragmentInternal.js +3 -3
  32. package/lib/relay-hooks/useRelayLoggingContext.js +9 -0
  33. package/package.json +3 -3
  34. package/relay-hooks/EntryPointContainer.react.js.flow +13 -17
  35. package/relay-hooks/EntryPointTypes.flow.js.flow +7 -4
  36. package/relay-hooks/MatchContainer.js.flow +1 -1
  37. package/relay-hooks/legacy/FragmentResource.js.flow +3 -6
  38. package/relay-hooks/legacy/useBlockingPaginationFragment.js.flow +2 -17
  39. package/relay-hooks/legacy/useRefetchableFragmentNode.js.flow +1 -1
  40. package/relay-hooks/loadEntryPoint.js.flow +6 -0
  41. package/relay-hooks/loadQuery.js.flow +20 -4
  42. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +18 -3
  43. package/relay-hooks/readFragmentInternal.js.flow +6 -6
  44. package/relay-hooks/useEntryPointLoader.js.flow +5 -3
  45. package/relay-hooks/useFragment.js.flow +1 -0
  46. package/relay-hooks/useFragmentInternal.js.flow +2 -0
  47. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +26 -6
  48. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +53 -14
  49. package/relay-hooks/useLazyLoadQuery.js.flow +62 -13
  50. package/relay-hooks/useLazyLoadQueryNode.js.flow +1 -1
  51. package/relay-hooks/useLoadMoreFunction.js.flow +7 -7
  52. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +5 -7
  53. package/relay-hooks/usePaginationFragment.js.flow +6 -10
  54. package/relay-hooks/usePrefetchableForwardPaginationFragment.js.flow +436 -0
  55. package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +15 -13
  56. package/relay-hooks/usePreloadedQuery.js.flow +2 -1
  57. package/relay-hooks/useQueryLoader.js.flow +6 -2
  58. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +3 -1
  59. package/relay-hooks/useRefetchableFragment.js.flow +1 -0
  60. package/relay-hooks/useRefetchableFragmentInternal.js.flow +3 -3
  61. package/relay-hooks/useRelayEnvironment.js.flow +22 -0
  62. package/relay-hooks/useRelayLoggingContext.js.flow +21 -0
  63. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +1 -0
  64. package/react-relay-hooks.js +0 -4
  65. package/react-relay-hooks.min.js +0 -9
  66. package/react-relay-legacy.js +0 -4
  67. package/react-relay-legacy.min.js +0 -9
  68. package/react-relay.js +0 -4
  69. package/react-relay.min.js +0 -9
@@ -135,6 +135,8 @@ hook useLoadMoreFunction_EXPERIMENTAL<TVariables: Variables>(
135
135
  connectionPathInFragmentData,
136
136
  );
137
137
 
138
+ const isRequestInvalid = fragmentData == null || isParentQueryActive;
139
+
138
140
  const isMountedRef = useIsMountedRef();
139
141
  const loadMore = useCallback(
140
142
  (
@@ -164,11 +166,8 @@ hook useLoadMoreFunction_EXPERIMENTAL<TVariables: Variables>(
164
166
  }
165
167
 
166
168
  const fragmentSelector = getSelector(fragmentNode, fragmentRef);
167
- if (
168
- fetchStatusRef.current.kind === 'fetching' ||
169
- fragmentData == null ||
170
- isParentQueryActive
171
- ) {
169
+
170
+ if (fetchStatusRef.current.kind === 'fetching' || isRequestInvalid) {
172
171
  if (fragmentSelector == null) {
173
172
  warning(
174
173
  false,
@@ -267,8 +266,7 @@ hook useLoadMoreFunction_EXPERIMENTAL<TVariables: Variables>(
267
266
  identifierValue,
268
267
  direction,
269
268
  cursor,
270
- isParentQueryActive,
271
- fragmentData,
269
+ isRequestInvalid,
272
270
  fragmentNode.name,
273
271
  fragmentRef,
274
272
  componentDisplayName,
@@ -16,8 +16,6 @@ import type {Options} from './useRefetchableFragmentInternal';
16
16
  import type {
17
17
  Disposable,
18
18
  FragmentType,
19
- GraphQLResponse,
20
- Observer,
21
19
  RefetchableFragment,
22
20
  Variables,
23
21
  } from 'relay-runtime';
@@ -28,6 +26,7 @@ const useRelayEnvironment = require('./useRelayEnvironment');
28
26
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
29
27
  const {useCallback, useDebugValue, useState} = require('react');
30
28
  const {
29
+ RelayFeatureFlags,
31
30
  getFragment,
32
31
  getFragmentIdentifier,
33
32
  getPaginationMetadata,
@@ -145,6 +144,7 @@ hook usePaginationFragment<
145
144
  if (__DEV__) {
146
145
  // eslint-disable-next-line react-hooks/rules-of-hooks
147
146
  // $FlowFixMe[react-rule-hook]
147
+ // $FlowFixMe[react-rule-hook-conditional]
148
148
  useDebugValue({
149
149
  fragment: fragmentNode.name,
150
150
  data: fragmentData,
@@ -168,14 +168,7 @@ hook usePaginationFragment<
168
168
  }
169
169
 
170
170
  hook useLoadMore<TVariables: Variables>(
171
- args: $Diff<
172
- UseLoadMoreFunctionArgs,
173
- {
174
- observer: Observer<GraphQLResponse>,
175
- onReset: () => void,
176
- ...
177
- },
178
- >,
171
+ args: Omit<UseLoadMoreFunctionArgs, 'observer' | 'onReset'>,
179
172
  ): [LoadMoreFn<TVariables>, boolean, boolean, () => void] {
180
173
  const environment = useRelayEnvironment();
181
174
  const [isLoadingMore, reallySetIsLoadingMore] = useState(false);
@@ -196,6 +189,9 @@ hook useLoadMore<TVariables: Variables>(
196
189
  start: () => setIsLoadingMore(true),
197
190
  complete: () => setIsLoadingMore(false),
198
191
  error: () => setIsLoadingMore(false),
192
+ unsubscribe: RelayFeatureFlags.ENABLE_USE_PAGINATION_IS_LOADING_FIX
193
+ ? () => setIsLoadingMore(false)
194
+ : undefined,
199
195
  };
200
196
  const handleReset = () => setIsLoadingMore(false);
201
197
  const [loadMore, hasMore, disposeFetch] = useLoadMoreFunction<TVariables>({
@@ -0,0 +1,436 @@
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 {RefetchFn} from './useRefetchableFragment';
15
+ import type {Options} from './useRefetchableFragmentInternal';
16
+ import type {FragmentType, Variables} from 'relay-runtime';
17
+ import type {PrefetchableRefetchableFragment} from 'relay-runtime';
18
+
19
+ const useFragment = require('./useFragment');
20
+ const useLoadMoreFunction = require('./useLoadMoreFunction');
21
+ const useRefetchableFragmentInternal = require('./useRefetchableFragmentInternal');
22
+ const useRelayEnvironment = require('./useRelayEnvironment');
23
+ const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
24
+ const invariant = require('invariant');
25
+ const {
26
+ useCallback,
27
+ useDebugValue,
28
+ useEffect,
29
+ useLayoutEffect,
30
+ useMemo,
31
+ useRef,
32
+ useState,
33
+ } = require('react');
34
+ const {
35
+ getFragment,
36
+ getFragmentIdentifier,
37
+ getPaginationMetadata,
38
+ } = require('relay-runtime');
39
+ const {
40
+ ConnectionInterface,
41
+ RelayFeatureFlags,
42
+ getSelector,
43
+ getValueAtPath,
44
+ } = require('relay-runtime');
45
+
46
+ type LoadMoreFn<TVariables: Variables> = (
47
+ count: number,
48
+ options?: {
49
+ onComplete?: (Error | null) => void,
50
+ UNSTABLE_extraVariables?: Partial<TVariables>,
51
+ },
52
+ ) => void;
53
+
54
+ export type ReturnType<TVariables, TData, TEdgeData, TKey> = {
55
+ // NOTE: This type ensures that the type of the returned data is either:
56
+ // - nullable if the provided ref type is nullable
57
+ // - non-nullable if the provided ref type is non-nullable
58
+ data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}]
59
+ ? TData
60
+ : ?TData,
61
+ loadNext: LoadMoreFn<TVariables>,
62
+ hasNext: boolean,
63
+ isLoadingNext: boolean,
64
+ refetch: RefetchFn<TVariables, TKey>,
65
+ edges: TEdgeData,
66
+ };
67
+
68
+ type LoadMoreOptions<TVariables> = {
69
+ UNSTABLE_extraVariables?: Partial<TVariables>,
70
+ onComplete?: (Error | null) => void,
71
+ };
72
+
73
+ export type GetExtraVariablesFn<TEdgeData, TData, TVariables, TKey> = ({
74
+ hasNext: boolean,
75
+ data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}]
76
+ ? TData
77
+ : ?TData,
78
+ getServerEdges: () => TEdgeData,
79
+ }) => Partial<TVariables>;
80
+
81
+ hook usePrefetchableForwardPaginationFragment<
82
+ TFragmentType: FragmentType,
83
+ TVariables: Variables,
84
+ TData,
85
+ TEdgeData,
86
+ TKey: ?{+$fragmentSpreads: TFragmentType, ...},
87
+ >(
88
+ fragmentInput: PrefetchableRefetchableFragment<
89
+ TFragmentType,
90
+ TData,
91
+ TEdgeData,
92
+ TVariables,
93
+ >,
94
+ parentFragmentRef: TKey,
95
+ bufferSize: number,
96
+ initialSize?: ?number,
97
+ prefetchingLoadMoreOptions?: {
98
+ UNSTABLE_extraVariables?:
99
+ | Partial<TVariables>
100
+ | GetExtraVariablesFn<TEdgeData, TData, TVariables, TKey>,
101
+ onComplete?: (Error | null) => void,
102
+ },
103
+ minimalFetchSize: number = 1,
104
+ disablePrefetching?: boolean = false,
105
+ ): ReturnType<TVariables, TData, TEdgeData, TKey> {
106
+ const fragmentNode = getFragment(fragmentInput);
107
+ useStaticFragmentNodeWarning(
108
+ fragmentNode,
109
+ 'first argument of usePrefetchableForwardPaginationFragment()',
110
+ );
111
+ const componentDisplayName = 'usePrefetchableForwardPaginationFragment()';
112
+
113
+ const {connectionPathInFragmentData, paginationRequest, paginationMetadata} =
114
+ getPaginationMetadata(fragmentNode, componentDisplayName);
115
+
116
+ const {fragmentData, fragmentRef, refetch} = useRefetchableFragmentInternal<
117
+ {variables: TVariables, response: TData},
118
+ {data?: TData},
119
+ >(fragmentNode, parentFragmentRef, componentDisplayName);
120
+ // TODO: Get rid of `getFragmentIdentifier`
121
+ const fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef);
122
+
123
+ const edgeKeys = useMemo(() => {
124
+ const connection = getValueAtPath(
125
+ fragmentData,
126
+ connectionPathInFragmentData,
127
+ );
128
+ if (connection == null) {
129
+ return null;
130
+ }
131
+ const {EDGES} = ConnectionInterface.get();
132
+ // $FlowFixMe[incompatible-use]
133
+ return connection[EDGES];
134
+ }, [connectionPathInFragmentData, fragmentData]);
135
+
136
+ const sourceSize = edgeKeys == null ? -1 : edgeKeys.length;
137
+
138
+ const [_numInUse, setNumInUse] = useState(
139
+ initialSize != null ? initialSize : sourceSize,
140
+ );
141
+ let numInUse = _numInUse;
142
+ // We can only reset the source size when the component is
143
+ // updated with new edgeKeys
144
+ if (_numInUse === -1 && sourceSize !== -1) {
145
+ numInUse = initialSize != null ? initialSize : sourceSize;
146
+ setNumInUse(numInUse);
147
+ }
148
+
149
+ const environment = useRelayEnvironment();
150
+ const [isLoadingMore, reallySetIsLoadingMore] = useState(false);
151
+ const [isRefetching, setIsRefetching] = useState(false);
152
+ const availableSizeRef = useRef(0);
153
+ // Schedule this update since it must be observed by components at the same
154
+ // batch as when hasNext changes. hasNext is read from the store and store
155
+ // updates are scheduled, so this must be scheduled too.
156
+ const setIsLoadingMore = useCallback(
157
+ (value: boolean) => {
158
+ const schedule = environment.getScheduler()?.schedule;
159
+ if (schedule) {
160
+ schedule(() => {
161
+ reallySetIsLoadingMore(value);
162
+ });
163
+ } else {
164
+ reallySetIsLoadingMore(value);
165
+ }
166
+ },
167
+ [environment],
168
+ );
169
+
170
+ // `isLoadingMore` state is updated in a low priority, internally we need
171
+ // to synchronously get the loading state to decide whether to load more
172
+ const isLoadingMoreRef = useRef(false);
173
+
174
+ const observer = useMemo(() => {
175
+ function setIsLoadingFalse() {
176
+ isLoadingMoreRef.current = false;
177
+ setIsLoadingMore(false);
178
+ }
179
+ return {
180
+ start: () => {
181
+ isLoadingMoreRef.current = true;
182
+ // We want to make sure that `isLoadingMore` is updated immediately, to avoid
183
+ // product code triggering multiple `loadMore` calls
184
+ reallySetIsLoadingMore(true);
185
+ },
186
+ complete: setIsLoadingFalse,
187
+ error: setIsLoadingFalse,
188
+ unsubscribe: RelayFeatureFlags.ENABLE_USE_PAGINATION_IS_LOADING_FIX
189
+ ? setIsLoadingFalse
190
+ : undefined,
191
+ };
192
+ }, [setIsLoadingMore]);
193
+ const handleReset = useCallback(() => {
194
+ if (!isRefetching) {
195
+ // Do not reset items count during refetching
196
+ const schedule = environment.getScheduler()?.schedule;
197
+ if (schedule) {
198
+ schedule(() => {
199
+ setNumInUse(-1);
200
+ });
201
+ } else {
202
+ setNumInUse(-1);
203
+ }
204
+ }
205
+ isLoadingMoreRef.current = false;
206
+ setIsLoadingMore(false);
207
+ }, [environment, isRefetching, setIsLoadingMore]);
208
+
209
+ const [loadMore, hasNext, disposeFetchNext] = useLoadMoreFunction<TVariables>(
210
+ {
211
+ componentDisplayName,
212
+ connectionPathInFragmentData,
213
+ direction: 'forward',
214
+ fragmentData,
215
+ fragmentIdentifier,
216
+ fragmentNode,
217
+ fragmentRef,
218
+ paginationMetadata,
219
+ paginationRequest,
220
+ observer,
221
+ onReset: handleReset,
222
+ },
223
+ );
224
+
225
+ useLayoutEffect(() => {
226
+ // Make sure `availableSize` is updated before `showMore` from current render can be called
227
+ availableSizeRef.current = sourceSize - numInUse;
228
+ }, [numInUse, sourceSize]);
229
+
230
+ const prefetchingUNSTABLE_extraVariables =
231
+ prefetchingLoadMoreOptions?.UNSTABLE_extraVariables;
232
+ const prefetchingOnComplete = prefetchingLoadMoreOptions?.onComplete;
233
+
234
+ const showMore = useCallback(
235
+ (numToAdd: number, options?: LoadMoreOptions<TVariables>) => {
236
+ // Matches the behavior of `usePaginationFragment`. If there is a `loadMore` ongoing,
237
+ // the hook handles making the `loadMore` a no-op.
238
+ if (!isLoadingMoreRef.current || availableSizeRef.current >= 0) {
239
+ // Preemtively update `availableSizeRef`, so if two `loadMore` is called in the same tick,
240
+ // a second `loadMore` can be no-op
241
+ availableSizeRef.current -= numToAdd;
242
+
243
+ setNumInUse(lastNumInUse => {
244
+ return lastNumInUse + numToAdd;
245
+ });
246
+
247
+ // If the product needs more items from network, load the amount needed to fullfil
248
+ // the requirement and cache, capped at the current amount defined by product
249
+ if (!isLoadingMoreRef.current && availableSizeRef.current < 0) {
250
+ loadMore(
251
+ Math.max(
252
+ minimalFetchSize,
253
+ Math.min(numToAdd, bufferSize - availableSizeRef.current),
254
+ ),
255
+ // Keep options For backward compatibility
256
+ options ?? {
257
+ onComplete: prefetchingOnComplete,
258
+ UNSTABLE_extraVariables:
259
+ typeof prefetchingUNSTABLE_extraVariables === 'function'
260
+ ? // $FlowFixMe[incompatible-call]
261
+ prefetchingUNSTABLE_extraVariables({
262
+ hasNext,
263
+ // $FlowFixMe[incompatible-call]
264
+ data: fragmentData,
265
+ getServerEdges: () => {
266
+ const selector = getSelector(
267
+ // $FlowFixMe[incompatible-call]
268
+ edgesFragment,
269
+ edgeKeys,
270
+ );
271
+ if (selector == null) {
272
+ // $FlowFixMe[incompatible-call]
273
+ return [];
274
+ }
275
+ invariant(
276
+ selector.kind === 'PluralReaderSelector',
277
+ 'Expected a plural selector',
278
+ );
279
+ // $FlowFixMe[incompatible-call]
280
+ return selector.selectors.map(
281
+ sel => environment.lookup(sel).data,
282
+ );
283
+ },
284
+ })
285
+ : prefetchingUNSTABLE_extraVariables,
286
+ },
287
+ );
288
+ }
289
+ }
290
+ },
291
+ [
292
+ bufferSize,
293
+ loadMore,
294
+ minimalFetchSize,
295
+ edgeKeys,
296
+ fragmentData,
297
+ prefetchingUNSTABLE_extraVariables,
298
+ prefetchingOnComplete,
299
+ ],
300
+ );
301
+
302
+ const edgesFragment = fragmentInput.metadata?.refetch?.edgesFragment;
303
+ invariant(
304
+ edgesFragment != null,
305
+ 'usePrefetchableForwardPaginationFragment: Expected the edge fragment to be defined, ' +
306
+ 'please make sure you have added `prefetchable_pagination: true` to `@connection`',
307
+ );
308
+
309
+ // Always try to keep `bufferSize` items in the buffer
310
+ // Or load the number of items that have been registred to show
311
+ useEffect(() => {
312
+ if (
313
+ // Check the ref to avoid infinite `loadMore`, when a `loadMore` has started,
314
+ // but `isLoadingMore` isn't updated
315
+ !isLoadingMoreRef.current &&
316
+ // Check the original `isLoadingMore` so when `loadMore` is called, the internal
317
+ // `loadMore` hook has been updated with the latest cursor
318
+ !isLoadingMore &&
319
+ !isRefetching &&
320
+ !disablePrefetching &&
321
+ hasNext &&
322
+ (sourceSize - numInUse < bufferSize || numInUse > sourceSize)
323
+ ) {
324
+ const onComplete = prefetchingOnComplete;
325
+ loadMore(
326
+ Math.max(
327
+ bufferSize - Math.max(sourceSize - numInUse, 0),
328
+ numInUse - sourceSize,
329
+ minimalFetchSize,
330
+ ),
331
+ {
332
+ onComplete,
333
+ UNSTABLE_extraVariables:
334
+ typeof prefetchingUNSTABLE_extraVariables === 'function'
335
+ ? // $FlowFixMe[incompatible-call]
336
+ prefetchingUNSTABLE_extraVariables({
337
+ hasNext,
338
+ // $FlowFixMe[incompatible-call]
339
+ data: fragmentData,
340
+ getServerEdges: () => {
341
+ const selector = getSelector(edgesFragment, edgeKeys);
342
+ if (selector == null) {
343
+ // $FlowFixMe[incompatible-call]
344
+ return [];
345
+ }
346
+ invariant(
347
+ selector.kind === 'PluralReaderSelector',
348
+ 'Expected a plural selector',
349
+ );
350
+ // $FlowFixMe[incompatible-call]
351
+ return selector.selectors.map(
352
+ sel => environment.lookup(sel).data,
353
+ );
354
+ },
355
+ })
356
+ : prefetchingUNSTABLE_extraVariables,
357
+ },
358
+ );
359
+ }
360
+ }, [
361
+ hasNext,
362
+ bufferSize,
363
+ isRefetching,
364
+ loadMore,
365
+ numInUse,
366
+ prefetchingUNSTABLE_extraVariables,
367
+ prefetchingOnComplete,
368
+ sourceSize,
369
+ edgeKeys,
370
+ isLoadingMore,
371
+ minimalFetchSize,
372
+ environment,
373
+ edgesFragment,
374
+ ]);
375
+
376
+ const realNumInUse = Math.min(numInUse, sourceSize);
377
+
378
+ const derivedEdgeKeys: $ReadOnlyArray<mixed> = useMemo(
379
+ () => edgeKeys?.slice(0, realNumInUse) ?? [],
380
+ [edgeKeys, realNumInUse],
381
+ );
382
+
383
+ // $FlowExpectedError[incompatible-call] - we know derivedEdgeKeys are the correct keys
384
+ const edges: TEdgeData = useFragment(edgesFragment, derivedEdgeKeys);
385
+
386
+ const refetchPagination = useCallback(
387
+ (variables: TVariables, options?: Options) => {
388
+ disposeFetchNext();
389
+ setIsRefetching(true);
390
+ return refetch(variables, {
391
+ ...options,
392
+ onComplete: maybeError => {
393
+ // Need to be batched with the store update
394
+ const schedule = environment.getScheduler()?.schedule;
395
+ if (schedule) {
396
+ schedule(() => {
397
+ setIsRefetching(false);
398
+ setNumInUse(-1);
399
+ });
400
+ } else {
401
+ setIsRefetching(false);
402
+ setNumInUse(-1);
403
+ }
404
+ options?.onComplete?.(maybeError);
405
+ },
406
+ __environment: undefined,
407
+ });
408
+ },
409
+ [disposeFetchNext, environment, refetch],
410
+ );
411
+
412
+ if (__DEV__) {
413
+ // $FlowFixMe[react-rule-hook]
414
+ // $FlowFixMe[react-rule-hook-conditional]
415
+ useDebugValue({
416
+ fragment: fragmentNode.name,
417
+ data: fragmentData,
418
+ hasNext,
419
+ isLoadingNext: isLoadingMore,
420
+ });
421
+ }
422
+
423
+ return {
424
+ edges,
425
+ // $FlowFixMe[incompatible-return]
426
+ data: fragmentData,
427
+ loadNext: showMore,
428
+ hasNext: hasNext || sourceSize > numInUse,
429
+ // Only reflect `isLoadingMore` if the product depends on it, do not refelect
430
+ // `isLoaindgMore` state if it is for fufilling the buffer
431
+ isLoadingNext: isLoadingMore && numInUse > sourceSize,
432
+ refetch: refetchPagination,
433
+ };
434
+ }
435
+
436
+ module.exports = usePrefetchableForwardPaginationFragment;
@@ -38,6 +38,7 @@ const {
38
38
  } = require('relay-runtime');
39
39
  const {
40
40
  ConnectionInterface,
41
+ RelayFeatureFlags,
41
42
  getSelector,
42
43
  getValueAtPath,
43
44
  } = require('relay-runtime');
@@ -170,25 +171,25 @@ hook usePrefetchableForwardPaginationFragment_EXPERIMENTAL<
170
171
  // to synchronously get the loading state to decide whether to load more
171
172
  const isLoadingMoreRef = useRef(false);
172
173
 
173
- const observer = useMemo(
174
- () => ({
174
+ const observer = useMemo(() => {
175
+ function setIsLoadingFalse() {
176
+ isLoadingMoreRef.current = false;
177
+ setIsLoadingMore(false);
178
+ }
179
+ return {
175
180
  start: () => {
176
181
  isLoadingMoreRef.current = true;
177
182
  // We want to make sure that `isLoadingMore` is updated immediately, to avoid
178
183
  // product code triggering multiple `loadMore` calls
179
184
  reallySetIsLoadingMore(true);
180
185
  },
181
- complete: () => {
182
- isLoadingMoreRef.current = false;
183
- setIsLoadingMore(false);
184
- },
185
- error: () => {
186
- isLoadingMoreRef.current = false;
187
- setIsLoadingMore(false);
188
- },
189
- }),
190
- [setIsLoadingMore],
191
- );
186
+ complete: setIsLoadingFalse,
187
+ error: setIsLoadingFalse,
188
+ unsubscribe: RelayFeatureFlags.ENABLE_USE_PAGINATION_IS_LOADING_FIX
189
+ ? setIsLoadingFalse
190
+ : undefined,
191
+ };
192
+ }, [setIsLoadingMore]);
192
193
  const handleReset = useCallback(() => {
193
194
  if (!isRefetching) {
194
195
  // Do not reset items count during refetching
@@ -409,6 +410,7 @@ hook usePrefetchableForwardPaginationFragment_EXPERIMENTAL<
409
410
 
410
411
  if (__DEV__) {
411
412
  // $FlowFixMe[react-rule-hook]
413
+ // $FlowFixMe[react-rule-hook-conditional]
412
414
  useDebugValue({
413
415
  fragment: fragmentNode.name,
414
416
  data: fragmentData,
@@ -126,7 +126,7 @@ hook usePreloadedQuery<
126
126
  // context, we cannot re-use the existing preloaded query.
127
127
  // Instead, we need to fall back and re-execute and de-dupe the query with
128
128
  // the new environment (at render time).
129
- // TODO T68036756 track occurences of this warning and turn it into a hard error
129
+ // TODO T68036756 track occurrences of this warning and turn it into a hard error
130
130
  warning(
131
131
  false,
132
132
  'usePreloadedQuery(): usePreloadedQuery was passed a preloaded query ' +
@@ -160,6 +160,7 @@ hook usePreloadedQuery<
160
160
  if (__DEV__) {
161
161
  // eslint-disable-next-line react-hooks/rules-of-hooks
162
162
  // $FlowFixMe[react-rule-hook]
163
+ // $FlowFixMe[react-rule-hook-conditional]
163
164
  useDebugValue({
164
165
  query: preloadedQuery.name,
165
166
  variables: preloadedQuery.variables,
@@ -46,7 +46,9 @@ export type UseQueryLoaderLoadQueryOptions = $ReadOnly<{
46
46
  export type NullQueryReference = {
47
47
  kind: 'NullQueryReference',
48
48
  };
49
- const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
49
+ const initialNullQueryReferenceState: NullQueryReference = {
50
+ kind: 'NullQueryReference',
51
+ };
50
52
 
51
53
  function requestIsLiveQuery<
52
54
  TVariables: Variables,
@@ -119,12 +121,14 @@ hook useQueryLoader<TVariables: Variables, TData, TRawResponse: ?{...} = void>(
119
121
  ): UseQueryLoaderHookReturnType<TVariables, TData> {
120
122
  if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
121
123
  // $FlowFixMe[react-rule-hook] - the condition is static
124
+ // $FlowFixMe[react-rule-hook-conditional]
122
125
  return useQueryLoader_EXPERIMENTAL(
123
126
  preloadableRequest,
124
127
  initialQueryReference,
125
128
  );
126
129
  }
127
130
  // $FlowFixMe[react-rule-hook] - the condition is static
131
+ // $FlowFixMe[react-rule-hook-conditional]
128
132
  return useQueryLoader_CURRENT(preloadableRequest, initialQueryReference);
129
133
  }
130
134
 
@@ -232,7 +236,7 @@ hook useQueryLoader_CURRENT<
232
236
  useEffect(() => {
233
237
  return () => {
234
238
  // Attempt to detect if the component was
235
- // hidden (by Offscreen API), or fast refresh occured;
239
+ // hidden (by Offscreen API), or fast refresh occurred;
236
240
  // Only in these situations would the effect cleanup
237
241
  // for "unmounting" run multiple times, so if
238
242
  // we are ever able to read this ref with a value
@@ -35,7 +35,9 @@ const {
35
35
  } = require('react');
36
36
  const {getRequest} = require('relay-runtime');
37
37
 
38
- const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
38
+ const initialNullQueryReferenceState: NullQueryReference = {
39
+ kind: 'NullQueryReference',
40
+ };
39
41
 
40
42
  function requestIsLiveQuery<
41
43
  TVariables: Variables,
@@ -85,6 +85,7 @@ hook useRefetchableFragment<
85
85
  if (__DEV__) {
86
86
  // eslint-disable-next-line react-hooks/rules-of-hooks
87
87
  // $FlowFixMe[react-rule-hook]
88
+ // $FlowFixMe[react-rule-hook-conditional]
88
89
  useDebugValue({fragment: fragmentNode.name, data: fragmentData});
89
90
  }
90
91
  // $FlowFixMe[incompatible-return]
@@ -160,7 +160,7 @@ function reducer(state: RefetchState, action: Action): RefetchState {
160
160
  }
161
161
  }
162
162
 
163
- hook useRefetchableFragmentNode<
163
+ hook useRefetchableFragmentInternal<
164
164
  TQuery: OperationType,
165
165
  TKey: ?{+$data?: mixed, ...},
166
166
  >(
@@ -582,7 +582,7 @@ if (__DEV__) {
582
582
  fragmentNode: ReaderFragment,
583
583
  componentDisplayName: string,
584
584
  ): void {
585
- if (previousIDAndTypename == null) {
585
+ if (previousIDAndTypename == null || refetchedFragmentRef == null) {
586
586
  return;
587
587
  }
588
588
  const {ID_KEY} = require('relay-runtime');
@@ -605,4 +605,4 @@ if (__DEV__) {
605
605
  };
606
606
  }
607
607
 
608
- module.exports = useRefetchableFragmentNode;
608
+ module.exports = useRefetchableFragmentInternal;