react-relay 18.0.0 → 18.2.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/ReactRelayContext.js +1 -1
- package/ReactRelayFragmentContainer.js.flow +2 -5
- package/buildReactRelayContainer.js.flow +1 -0
- package/hooks.js +1 -1
- package/index.js +1 -1
- package/index.js.flow +3 -0
- package/legacy.js +1 -1
- package/lib/index.js +2 -0
- package/lib/relay-hooks/getConnectionState.js +47 -0
- package/lib/relay-hooks/legacy/FragmentResource.js +3 -8
- package/lib/relay-hooks/loadQuery.js +5 -14
- package/lib/relay-hooks/readFragmentInternal.js +2 -4
- package/lib/relay-hooks/useFragmentInternal.js +1 -1
- package/lib/relay-hooks/useFragmentInternal_CURRENT.js +3 -10
- package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +6 -28
- package/lib/relay-hooks/useLoadMoreFunction.js +10 -43
- package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +130 -0
- package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +227 -0
- package/lib/relay-hooks/useQueryLoader.js +8 -0
- package/lib/relay-hooks/useQueryLoader_EXPERIMENTAL.js +120 -0
- package/package.json +2 -2
- package/react-relay-hooks.js +2 -2
- package/react-relay-hooks.min.js +2 -2
- package/react-relay-legacy.js +1 -1
- package/react-relay-legacy.min.js +1 -1
- package/react-relay.js +2 -2
- package/react-relay.min.js +2 -2
- package/relay-hooks/EntryPointTypes.flow.js.flow +2 -2
- package/relay-hooks/MatchContainer.js.flow +1 -1
- package/relay-hooks/getConnectionState.js.flow +97 -0
- package/relay-hooks/legacy/FragmentResource.js.flow +4 -16
- package/relay-hooks/loadQuery.js.flow +30 -38
- package/relay-hooks/readFragmentInternal.js.flow +1 -10
- package/relay-hooks/useFragmentInternal.js.flow +1 -1
- package/relay-hooks/useFragmentInternal_CURRENT.js.flow +7 -22
- package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +8 -56
- package/relay-hooks/useLoadMoreFunction.js.flow +14 -80
- package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +280 -0
- package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +433 -0
- package/relay-hooks/useQueryLoader.js.flow +27 -3
- package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +253 -0
@@ -25,9 +25,10 @@ import type {
|
|
25
25
|
|
26
26
|
const {loadQuery} = require('./loadQuery');
|
27
27
|
const useIsMountedRef = require('./useIsMountedRef');
|
28
|
+
const useQueryLoader_EXPERIMENTAL = require('./useQueryLoader_EXPERIMENTAL');
|
28
29
|
const useRelayEnvironment = require('./useRelayEnvironment');
|
29
30
|
const {useCallback, useEffect, useRef, useState} = require('react');
|
30
|
-
const {getRequest} = require('relay-runtime');
|
31
|
+
const {RelayFeatureFlags, getRequest} = require('relay-runtime');
|
31
32
|
|
32
33
|
export type LoaderFn<TQuery: OperationType> = (
|
33
34
|
variables: TQuery['variables'],
|
@@ -42,7 +43,7 @@ export type UseQueryLoaderLoadQueryOptions = $ReadOnly<{
|
|
42
43
|
// NullQueryReference needs to implement referential equality,
|
43
44
|
// so that multiple NullQueryReferences can be in the same set
|
44
45
|
// (corresponding to multiple calls to disposeQuery).
|
45
|
-
type NullQueryReference = {
|
46
|
+
export type NullQueryReference = {
|
46
47
|
kind: 'NullQueryReference',
|
47
48
|
};
|
48
49
|
const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
|
@@ -68,7 +69,7 @@ function requestIsLiveQuery<
|
|
68
69
|
return request.params.metadata.live !== undefined;
|
69
70
|
}
|
70
71
|
|
71
|
-
type UseQueryLoaderHookReturnType<
|
72
|
+
export type UseQueryLoaderHookReturnType<
|
72
73
|
TVariables: Variables,
|
73
74
|
TData,
|
74
75
|
TRawResponse: ?{...} = void,
|
@@ -115,6 +116,29 @@ hook useQueryLoader<TVariables: Variables, TData, TRawResponse: ?{...} = void>(
|
|
115
116
|
variables: TVariables,
|
116
117
|
rawResponse?: $NonMaybeType<TRawResponse>,
|
117
118
|
}>,
|
119
|
+
): UseQueryLoaderHookReturnType<TVariables, TData> {
|
120
|
+
if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
|
121
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
122
|
+
return useQueryLoader_EXPERIMENTAL(
|
123
|
+
preloadableRequest,
|
124
|
+
initialQueryReference,
|
125
|
+
);
|
126
|
+
}
|
127
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
128
|
+
return useQueryLoader_CURRENT(preloadableRequest, initialQueryReference);
|
129
|
+
}
|
130
|
+
|
131
|
+
hook useQueryLoader_CURRENT<
|
132
|
+
TVariables: Variables,
|
133
|
+
TData,
|
134
|
+
TRawResponse: ?{...} = void,
|
135
|
+
>(
|
136
|
+
preloadableRequest: Query<TVariables, TData, TRawResponse>,
|
137
|
+
initialQueryReference?: ?PreloadedQuery<{
|
138
|
+
response: TData,
|
139
|
+
variables: TVariables,
|
140
|
+
rawResponse?: $NonMaybeType<TRawResponse>,
|
141
|
+
}>,
|
118
142
|
): UseQueryLoaderHookReturnType<TVariables, TData> {
|
119
143
|
type QueryType = {
|
120
144
|
response: TData,
|
@@ -0,0 +1,253 @@
|
|
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 {
|
15
|
+
PreloadableConcreteRequest,
|
16
|
+
PreloadedQuery,
|
17
|
+
} from './EntryPointTypes.flow';
|
18
|
+
import type {
|
19
|
+
NullQueryReference,
|
20
|
+
UseQueryLoaderHookReturnType,
|
21
|
+
UseQueryLoaderLoadQueryOptions,
|
22
|
+
} from './useQueryLoader';
|
23
|
+
import type {OperationType, Query, Variables} from 'relay-runtime';
|
24
|
+
|
25
|
+
const {loadQuery} = require('./loadQuery');
|
26
|
+
const useIsMountedRef = require('./useIsMountedRef');
|
27
|
+
const useRelayEnvironment = require('./useRelayEnvironment');
|
28
|
+
const {
|
29
|
+
useCallback,
|
30
|
+
useEffect,
|
31
|
+
// $FlowFixMe[prop-missing] Remove once callers are migrated to official API
|
32
|
+
useInsertionEffect,
|
33
|
+
useRef,
|
34
|
+
useState,
|
35
|
+
} = require('react');
|
36
|
+
const {getRequest} = require('relay-runtime');
|
37
|
+
|
38
|
+
const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
|
39
|
+
|
40
|
+
function requestIsLiveQuery<
|
41
|
+
TVariables: Variables,
|
42
|
+
TData,
|
43
|
+
TRawResponse: ?{...} = void,
|
44
|
+
TQuery: OperationType = {
|
45
|
+
response: TData,
|
46
|
+
variables: TVariables,
|
47
|
+
rawResponse?: $NonMaybeType<TRawResponse>,
|
48
|
+
},
|
49
|
+
>(
|
50
|
+
preloadableRequest:
|
51
|
+
| Query<TVariables, TData, TRawResponse>
|
52
|
+
| PreloadableConcreteRequest<TQuery>,
|
53
|
+
): boolean {
|
54
|
+
if (preloadableRequest.kind === 'PreloadableConcreteRequest') {
|
55
|
+
return preloadableRequest.params.metadata.live !== undefined;
|
56
|
+
}
|
57
|
+
const request = getRequest(preloadableRequest);
|
58
|
+
return request.params.metadata.live !== undefined;
|
59
|
+
}
|
60
|
+
|
61
|
+
const CLEANUP_TIMEOUT = 1000 * 60 * 5; // 5 minutes;
|
62
|
+
|
63
|
+
hook useQueryLoader_EXPERIMENTAL<
|
64
|
+
TVariables: Variables,
|
65
|
+
TData,
|
66
|
+
TRawResponse: ?{...} = void,
|
67
|
+
>(
|
68
|
+
preloadableRequest: Query<TVariables, TData, TRawResponse>,
|
69
|
+
initialQueryReference?: ?PreloadedQuery<{
|
70
|
+
response: TData,
|
71
|
+
variables: TVariables,
|
72
|
+
rawResponse?: $NonMaybeType<TRawResponse>,
|
73
|
+
}>,
|
74
|
+
): UseQueryLoaderHookReturnType<TVariables, TData> {
|
75
|
+
type QueryType = {
|
76
|
+
response: TData,
|
77
|
+
variables: TVariables,
|
78
|
+
rawResponse?: $NonMaybeType<TRawResponse>,
|
79
|
+
};
|
80
|
+
|
81
|
+
/**
|
82
|
+
* We want to always call `queryReference.dispose()` for every call to
|
83
|
+
* `setQueryReference(loadQuery(...))` so that no leaks of data in Relay stores
|
84
|
+
* will occur.
|
85
|
+
*
|
86
|
+
* However, a call to `setState(newState)` is not always followed by a commit where
|
87
|
+
* this value is reflected in the state. Thus, we cannot reliably clean up each
|
88
|
+
* ref with `useEffect(() => () => queryReference.dispose(), [queryReference])`.
|
89
|
+
*
|
90
|
+
* Instead, we keep track of each call to `loadQuery` in a ref.
|
91
|
+
* Relying on the fact that if a state change commits, no state changes that were
|
92
|
+
* initiated prior to the currently committing state change will ever subsequently
|
93
|
+
* commit, we can safely dispose of all preloaded query references
|
94
|
+
* associated with state changes initiated prior to the currently committing state
|
95
|
+
* change.
|
96
|
+
*
|
97
|
+
* Finally, when the hook unmounts, we also dispose of all remaining uncommitted
|
98
|
+
* query references.
|
99
|
+
*/
|
100
|
+
|
101
|
+
const initialQueryReferenceInternal =
|
102
|
+
initialQueryReference ?? initialNullQueryReferenceState;
|
103
|
+
|
104
|
+
const environment = useRelayEnvironment();
|
105
|
+
|
106
|
+
const isMountedRef = useIsMountedRef();
|
107
|
+
const undisposedQueryReferencesRef = useRef<Set<
|
108
|
+
PreloadedQuery<QueryType> | NullQueryReference,
|
109
|
+
> | null>(null);
|
110
|
+
if (undisposedQueryReferencesRef.current == null) {
|
111
|
+
undisposedQueryReferencesRef.current = new Set([
|
112
|
+
initialQueryReferenceInternal,
|
113
|
+
]);
|
114
|
+
}
|
115
|
+
|
116
|
+
const [queryReference, setQueryReference] = useState<
|
117
|
+
PreloadedQuery<QueryType> | NullQueryReference,
|
118
|
+
>(() => initialQueryReferenceInternal);
|
119
|
+
|
120
|
+
const [previousInitialQueryReference, setPreviousInitialQueryReference] =
|
121
|
+
useState<PreloadedQuery<QueryType> | NullQueryReference>(
|
122
|
+
() => initialQueryReferenceInternal,
|
123
|
+
);
|
124
|
+
|
125
|
+
if (initialQueryReferenceInternal !== previousInitialQueryReference) {
|
126
|
+
// Rendering the query reference makes it "managed" by this hook, so
|
127
|
+
// we start keeping track of it so we can dispose it when it is no longer
|
128
|
+
// necessary here
|
129
|
+
// TODO(T78446637): Handle disposal of managed query references in
|
130
|
+
// components that were never mounted after rendering
|
131
|
+
// $FlowFixMe[react-rule-unsafe-ref]
|
132
|
+
undisposedQueryReferencesRef.current?.add(initialQueryReferenceInternal);
|
133
|
+
setPreviousInitialQueryReference(initialQueryReferenceInternal);
|
134
|
+
setQueryReference(initialQueryReferenceInternal);
|
135
|
+
}
|
136
|
+
|
137
|
+
const disposeQuery = useCallback(() => {
|
138
|
+
if (isMountedRef.current) {
|
139
|
+
undisposedQueryReferencesRef.current?.add(initialNullQueryReferenceState);
|
140
|
+
setQueryReference(initialNullQueryReferenceState);
|
141
|
+
}
|
142
|
+
}, [isMountedRef]);
|
143
|
+
|
144
|
+
const queryLoaderCallback = useCallback(
|
145
|
+
(variables: TVariables, options?: ?UseQueryLoaderLoadQueryOptions) => {
|
146
|
+
if (!isMountedRef.current) {
|
147
|
+
return;
|
148
|
+
}
|
149
|
+
const mergedOptions: ?UseQueryLoaderLoadQueryOptions =
|
150
|
+
options != null && options.hasOwnProperty('__environment')
|
151
|
+
? {
|
152
|
+
fetchPolicy: options.fetchPolicy,
|
153
|
+
networkCacheConfig: options.networkCacheConfig,
|
154
|
+
__nameForWarning: options.__nameForWarning,
|
155
|
+
}
|
156
|
+
: options;
|
157
|
+
const updatedQueryReference = loadQuery(
|
158
|
+
options?.__environment ?? environment,
|
159
|
+
preloadableRequest,
|
160
|
+
variables,
|
161
|
+
(mergedOptions: $FlowFixMe),
|
162
|
+
);
|
163
|
+
undisposedQueryReferencesRef.current?.add(updatedQueryReference);
|
164
|
+
setQueryReference(updatedQueryReference);
|
165
|
+
},
|
166
|
+
[environment, preloadableRequest, setQueryReference, isMountedRef],
|
167
|
+
);
|
168
|
+
|
169
|
+
const disposeAllRemainingQueryReferences = useCallback(
|
170
|
+
function disposeAllRemainingQueryReferences(
|
171
|
+
preloadableRequest: Query<TVariables, TData, TRawResponse>,
|
172
|
+
currentQueryReference:
|
173
|
+
| PreloadedQuery<QueryType>
|
174
|
+
| NullQueryReference
|
175
|
+
| null,
|
176
|
+
) {
|
177
|
+
const undisposedQueryReferences =
|
178
|
+
undisposedQueryReferencesRef.current ?? new Set();
|
179
|
+
// undisposedQueryReferences.current is never reassigned
|
180
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
181
|
+
for (const undisposedQueryReference of undisposedQueryReferences) {
|
182
|
+
if (undisposedQueryReference === currentQueryReference) {
|
183
|
+
continue;
|
184
|
+
}
|
185
|
+
if (undisposedQueryReference.kind !== 'NullQueryReference') {
|
186
|
+
if (requestIsLiveQuery(preloadableRequest)) {
|
187
|
+
undisposedQueryReference.dispose &&
|
188
|
+
undisposedQueryReference.dispose();
|
189
|
+
} else {
|
190
|
+
undisposedQueryReference.releaseQuery &&
|
191
|
+
undisposedQueryReference.releaseQuery();
|
192
|
+
}
|
193
|
+
}
|
194
|
+
}
|
195
|
+
},
|
196
|
+
[],
|
197
|
+
);
|
198
|
+
|
199
|
+
const cleanupTimerRef = useRef<?TimeoutID>(null);
|
200
|
+
useEffect(() => {
|
201
|
+
// When a new queryReference is committed, we iterate over all
|
202
|
+
// query references in undisposedQueryReferences and dispose all of
|
203
|
+
// the refs that aren't the currently committed one. This ensures
|
204
|
+
// that we don't leave any dangling query references for the
|
205
|
+
// case that loadQuery is called multiple times before commit; when
|
206
|
+
// this happens, multiple state updates will be scheduled, but only one
|
207
|
+
// will commit, meaning that we need to keep track of and dispose any
|
208
|
+
// query references that don't end up committing.
|
209
|
+
// - We are relying on the fact that sets iterate in insertion order, and we
|
210
|
+
// can remove items from a set as we iterate over it (i.e. no iterator
|
211
|
+
// invalidation issues.) Thus, it is safe to loop through
|
212
|
+
// undisposedQueryReferences until we find queryReference, and
|
213
|
+
// remove and dispose all previous references.
|
214
|
+
// - We are guaranteed to find queryReference in the set, because if a
|
215
|
+
// state update results in a commit, no state updates initiated prior to that
|
216
|
+
// one will be committed, and we are disposing and removing references
|
217
|
+
// associated with updates that were scheduled prior to the currently
|
218
|
+
// committing state change. (A useEffect callback is called during the commit
|
219
|
+
// phase.)
|
220
|
+
disposeAllRemainingQueryReferences(preloadableRequest, queryReference);
|
221
|
+
if (cleanupTimerRef.current != null) {
|
222
|
+
clearTimeout(cleanupTimerRef.current);
|
223
|
+
cleanupTimerRef.current = null;
|
224
|
+
}
|
225
|
+
return () => {
|
226
|
+
cleanupTimerRef.current = setTimeout(() => {
|
227
|
+
disposeAllRemainingQueryReferences(preloadableRequest, null);
|
228
|
+
}, CLEANUP_TIMEOUT);
|
229
|
+
};
|
230
|
+
}, [preloadableRequest, queryReference]);
|
231
|
+
|
232
|
+
// $FlowFixMe[not-a-function]
|
233
|
+
useInsertionEffect(() => {
|
234
|
+
// We use an insertion effect to ensure that we cleanup the final query
|
235
|
+
// reference when the component truly unmounts. Note that a regular
|
236
|
+
// useEffect may detach/reattach due to <Activity> multiple times, and
|
237
|
+
// we don't want to free queries that may be used when the component reveals
|
238
|
+
// again.
|
239
|
+
return () => {
|
240
|
+
cleanupTimerRef.current && clearTimeout(cleanupTimerRef.current);
|
241
|
+
cleanupTimerRef.current = null;
|
242
|
+
disposeAllRemainingQueryReferences(preloadableRequest, null);
|
243
|
+
};
|
244
|
+
}, [preloadableRequest]);
|
245
|
+
|
246
|
+
return [
|
247
|
+
queryReference.kind === 'NullQueryReference' ? null : queryReference,
|
248
|
+
queryLoaderCallback,
|
249
|
+
disposeQuery,
|
250
|
+
];
|
251
|
+
}
|
252
|
+
|
253
|
+
module.exports = useQueryLoader_EXPERIMENTAL;
|