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.
- package/ReactRelayContainerUtils.js.flow +2 -2
- package/ReactRelayContext.js +1 -1
- package/ReactRelayFragmentContainer.js.flow +5 -7
- package/ReactRelayLoggingContext.js.flow +21 -0
- package/ReactRelayPaginationContainer.js.flow +8 -8
- package/ReactRelayRefetchContainer.js.flow +8 -8
- package/ReactRelayTypes.js.flow +18 -11
- package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +1 -8
- package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +2 -5
- package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +2 -5
- package/buildReactRelayContainer.js.flow +4 -5
- package/hooks.js +1 -1
- package/index.js +1 -1
- package/index.js.flow +3 -3
- package/legacy.js +1 -1
- package/lib/ReactRelayLoggingContext.js +6 -0
- package/lib/index.js +2 -2
- package/lib/relay-hooks/legacy/FragmentResource.js +3 -3
- package/lib/relay-hooks/legacy/useRefetchableFragmentNode.js +1 -1
- package/lib/relay-hooks/loadEntryPoint.js +3 -0
- package/lib/relay-hooks/loadQuery.js +5 -3
- package/lib/relay-hooks/preloadQuery_DEPRECATED.js +7 -2
- package/lib/relay-hooks/readFragmentInternal.js +3 -3
- package/lib/relay-hooks/useFragmentInternal_CURRENT.js +12 -7
- package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +25 -10
- package/lib/relay-hooks/useLoadMoreFunction.js +3 -2
- package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +3 -2
- package/lib/relay-hooks/usePaginationFragment.js +5 -1
- package/lib/relay-hooks/usePrefetchableForwardPaginationFragment.js +228 -0
- package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +8 -8
- package/lib/relay-hooks/useRefetchableFragmentInternal.js +3 -3
- package/lib/relay-hooks/useRelayLoggingContext.js +9 -0
- package/package.json +3 -3
- package/relay-hooks/EntryPointContainer.react.js.flow +13 -17
- package/relay-hooks/EntryPointTypes.flow.js.flow +7 -4
- package/relay-hooks/MatchContainer.js.flow +1 -1
- package/relay-hooks/legacy/FragmentResource.js.flow +3 -6
- package/relay-hooks/legacy/useBlockingPaginationFragment.js.flow +2 -17
- package/relay-hooks/legacy/useRefetchableFragmentNode.js.flow +1 -1
- package/relay-hooks/loadEntryPoint.js.flow +6 -0
- package/relay-hooks/loadQuery.js.flow +20 -4
- package/relay-hooks/preloadQuery_DEPRECATED.js.flow +18 -3
- package/relay-hooks/readFragmentInternal.js.flow +6 -6
- package/relay-hooks/useEntryPointLoader.js.flow +5 -3
- package/relay-hooks/useFragment.js.flow +1 -0
- package/relay-hooks/useFragmentInternal.js.flow +2 -0
- package/relay-hooks/useFragmentInternal_CURRENT.js.flow +26 -6
- package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +53 -14
- package/relay-hooks/useLazyLoadQuery.js.flow +62 -13
- package/relay-hooks/useLazyLoadQueryNode.js.flow +1 -1
- package/relay-hooks/useLoadMoreFunction.js.flow +7 -7
- package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +5 -7
- package/relay-hooks/usePaginationFragment.js.flow +6 -10
- package/relay-hooks/usePrefetchableForwardPaginationFragment.js.flow +436 -0
- package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +15 -13
- package/relay-hooks/usePreloadedQuery.js.flow +2 -1
- package/relay-hooks/useQueryLoader.js.flow +6 -2
- package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +3 -1
- package/relay-hooks/useRefetchableFragment.js.flow +1 -0
- package/relay-hooks/useRefetchableFragmentInternal.js.flow +3 -3
- package/relay-hooks/useRelayEnvironment.js.flow +22 -0
- package/relay-hooks/useRelayLoggingContext.js.flow +21 -0
- package/relay-hooks/useStaticFragmentNodeWarning.js.flow +1 -0
- package/react-relay-hooks.js +0 -4
- package/react-relay-hooks.min.js +0 -9
- package/react-relay-legacy.js +0 -4
- package/react-relay-legacy.min.js +0 -9
- package/react-relay.js +0 -4
- 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
|
-
|
168
|
-
|
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
|
-
|
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:
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
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 = {
|
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
|
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 = {
|
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
|
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 =
|
608
|
+
module.exports = useRefetchableFragmentInternal;
|