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
@@ -62,6 +62,12 @@ function loadEntryPoint<
|
|
62
62
|
const {environmentProviderOptions, options, parameters, variables} =
|
63
63
|
query;
|
64
64
|
|
65
|
+
// $FlowFixMe[prop-missing] Exists for types that wrap EntryPoint
|
66
|
+
if (options?.includeIf === false) {
|
67
|
+
// don't preload this query since the includeIf is false
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
65
71
|
const environment = environmentProvider.getEnvironment(
|
66
72
|
environmentProviderOptions,
|
67
73
|
);
|
@@ -28,6 +28,7 @@ import type {
|
|
28
28
|
RequestIdentifier,
|
29
29
|
RequestParameters,
|
30
30
|
} from 'relay-runtime';
|
31
|
+
import type {OperationAvailability} from 'relay-runtime/store/RelayStoreTypes';
|
31
32
|
|
32
33
|
const invariant = require('invariant');
|
33
34
|
const {
|
@@ -42,7 +43,7 @@ const {
|
|
42
43
|
|
43
44
|
let fetchKey = 100001;
|
44
45
|
|
45
|
-
type QueryType<T> =
|
46
|
+
export type QueryType<T> =
|
46
47
|
T extends Query<infer V, infer D, infer RR>
|
47
48
|
? {
|
48
49
|
variables: V,
|
@@ -125,11 +126,12 @@ function loadQuery<
|
|
125
126
|
let networkError = null;
|
126
127
|
// makeNetworkRequest will immediately start a raw network request if
|
127
128
|
// one isn't already in flight and return an Observable that when
|
128
|
-
// subscribed to will replay the network events that have
|
129
|
+
// subscribed to will replay the network events that have occurred so far,
|
129
130
|
// as well as subsequent events.
|
130
131
|
let didMakeNetworkRequest = false;
|
131
132
|
const makeNetworkRequest = (
|
132
133
|
params: RequestParameters,
|
134
|
+
checkOperation?: () => OperationAvailability,
|
133
135
|
): Observable<GraphQLResponse> => {
|
134
136
|
// N.B. this function is called synchronously or not at all
|
135
137
|
// didMakeNetworkRequest is safe to rely on in the returned value
|
@@ -160,7 +162,16 @@ function loadQuery<
|
|
160
162
|
'raw-network-request-' + getRequestIdentifier(params, variables);
|
161
163
|
const observable = fetchQueryDeduped(environment, identifier, () => {
|
162
164
|
const network = environment.getNetwork();
|
163
|
-
return network.execute(
|
165
|
+
return network.execute(
|
166
|
+
params,
|
167
|
+
variables,
|
168
|
+
networkCacheConfig,
|
169
|
+
undefined,
|
170
|
+
undefined,
|
171
|
+
undefined,
|
172
|
+
undefined,
|
173
|
+
checkOperation,
|
174
|
+
);
|
164
175
|
});
|
165
176
|
|
166
177
|
const {unsubscribe} = observable.subscribe({
|
@@ -247,13 +258,18 @@ function loadQuery<
|
|
247
258
|
// then we do nothing.
|
248
259
|
const shouldFetch =
|
249
260
|
fetchPolicy !== 'store-or-network' ||
|
261
|
+
// environment.check can trigger store updates through missing field handlers,
|
262
|
+
// short circuiting the check avoids unnecessary updates
|
250
263
|
environment.check(operation).status !== 'available';
|
251
264
|
|
252
265
|
if (shouldFetch) {
|
253
266
|
executeDeduped(operation, () => {
|
254
267
|
// N.B. Since we have the operation synchronously available here,
|
255
268
|
// we can immediately fetch and execute the operation.
|
256
|
-
const networkObservable = makeNetworkRequest(
|
269
|
+
const networkObservable = makeNetworkRequest(
|
270
|
+
concreteRequest.params,
|
271
|
+
() => environment.check(operation),
|
272
|
+
);
|
257
273
|
const executeObservable = executeWithNetworkSource(
|
258
274
|
operation,
|
259
275
|
networkObservable,
|
@@ -24,6 +24,7 @@ import type {
|
|
24
24
|
GraphQLResponse,
|
25
25
|
GraphQLTaggedNode,
|
26
26
|
IEnvironment,
|
27
|
+
OperationAvailability,
|
27
28
|
OperationType,
|
28
29
|
Subscription,
|
29
30
|
} from 'relay-runtime';
|
@@ -167,12 +168,17 @@ function preloadQueryDeduped<TQuery: OperationType>(
|
|
167
168
|
}`;
|
168
169
|
const prevQueryEntry = pendingQueries.get(cacheKey);
|
169
170
|
|
170
|
-
|
171
|
-
|
171
|
+
function checkOperation(): OperationAvailability {
|
172
|
+
return query != null
|
172
173
|
? environment.check(
|
173
174
|
createOperationDescriptor(query, variables, networkCacheConfig),
|
174
175
|
)
|
175
176
|
: {status: 'missing'};
|
177
|
+
}
|
178
|
+
const availability =
|
179
|
+
fetchPolicy === STORE_OR_NETWORK_DEFAULT
|
180
|
+
? checkOperation()
|
181
|
+
: {status: 'missing'};
|
176
182
|
|
177
183
|
let nextQueryEntry: ?PendingQueryEntry;
|
178
184
|
if (availability.status === 'available' && query != null) {
|
@@ -203,7 +209,16 @@ function preloadQueryDeduped<TQuery: OperationType>(
|
|
203
209
|
}
|
204
210
|
} else if (prevQueryEntry == null || prevQueryEntry.kind !== 'network') {
|
205
211
|
// Should fetch but we're not already fetching: fetch!
|
206
|
-
const source = network.execute(
|
212
|
+
const source = network.execute(
|
213
|
+
params,
|
214
|
+
variables,
|
215
|
+
networkCacheConfig,
|
216
|
+
null,
|
217
|
+
undefined,
|
218
|
+
undefined,
|
219
|
+
undefined,
|
220
|
+
checkOperation,
|
221
|
+
);
|
207
222
|
const subject = new ReplaySubject<GraphQLResponse>();
|
208
223
|
nextQueryEntry = {
|
209
224
|
cacheKey,
|
@@ -83,13 +83,10 @@ function handlePotentialSnapshotErrorsForState(
|
|
83
83
|
state: FragmentState,
|
84
84
|
): void {
|
85
85
|
if (state.kind === 'singular') {
|
86
|
-
handlePotentialSnapshotErrors(
|
87
|
-
environment,
|
88
|
-
state.snapshot.errorResponseFields,
|
89
|
-
);
|
86
|
+
handlePotentialSnapshotErrors(environment, state.snapshot.fieldErrors);
|
90
87
|
} else if (state.kind === 'plural') {
|
91
88
|
for (const snapshot of state.snapshots) {
|
92
|
-
handlePotentialSnapshotErrors(environment, snapshot.
|
89
|
+
handlePotentialSnapshotErrors(environment, snapshot.fieldErrors);
|
93
90
|
}
|
94
91
|
}
|
95
92
|
}
|
@@ -216,7 +213,10 @@ function readFragmentInternal(
|
|
216
213
|
// Handle the queries for any missing client edges; this may suspend.
|
217
214
|
// FIXME handle client edges in parallel.
|
218
215
|
let clientEdgeQueries = null;
|
219
|
-
if (
|
216
|
+
if (
|
217
|
+
fragmentNode.metadata?.hasClientEdges === true ||
|
218
|
+
RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
|
219
|
+
) {
|
220
220
|
const missingClientEdges = getMissingClientEdges(state);
|
221
221
|
if (missingClientEdges?.length) {
|
222
222
|
clientEdgeQueries = ([]: Array<QueryResult>);
|
@@ -48,7 +48,9 @@ type UseEntryPointLoaderHookReturnType<
|
|
48
48
|
type NullEntryPointReference = {
|
49
49
|
kind: 'NullEntryPointReference',
|
50
50
|
};
|
51
|
-
const initialNullEntryPointReferenceState = {
|
51
|
+
const initialNullEntryPointReferenceState: NullEntryPointReference = {
|
52
|
+
kind: 'NullEntryPointReference',
|
53
|
+
};
|
52
54
|
|
53
55
|
hook useLoadEntryPoint<
|
54
56
|
TEntryPointParams: {...},
|
@@ -122,7 +124,7 @@ hook useLoadEntryPoint<
|
|
122
124
|
|
123
125
|
const disposeEntryPoint = useCallback(() => {
|
124
126
|
if (isMountedRef.current) {
|
125
|
-
const nullEntryPointReference = {
|
127
|
+
const nullEntryPointReference: NullEntryPointReference = {
|
126
128
|
kind: 'NullEntryPointReference',
|
127
129
|
};
|
128
130
|
undisposedEntryPointReferencesRef.current.add(nullEntryPointReference);
|
@@ -156,7 +158,7 @@ hook useLoadEntryPoint<
|
|
156
158
|
useEffect(() => {
|
157
159
|
return () => {
|
158
160
|
// Attempt to detect if the component was
|
159
|
-
// hidden (by Offscreen API), or fast refresh
|
161
|
+
// hidden (by Offscreen API), or fast refresh occurred;
|
160
162
|
// Only in these situations would the effect cleanup
|
161
163
|
// for "unmounting" run multiple times, so if
|
162
164
|
// we are ever able to read this ref with a value
|
@@ -54,6 +54,7 @@ hook useFragment(fragment: GraphQLTaggedNode, key: mixed): mixed {
|
|
54
54
|
if (__DEV__) {
|
55
55
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
56
56
|
// $FlowFixMe[react-rule-hook]
|
57
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
57
58
|
useDebugValue({fragment: fragmentNode.name, data});
|
58
59
|
}
|
59
60
|
return data;
|
@@ -26,6 +26,7 @@ hook useFragmentInternal(
|
|
26
26
|
): ?SelectorData | Array<?SelectorData> {
|
27
27
|
if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
|
28
28
|
// $FlowFixMe[react-rule-hook] - the condition is static
|
29
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
29
30
|
return useFragmentInternal_EXPERIMENTAL(
|
30
31
|
fragmentNode,
|
31
32
|
fragmentRef,
|
@@ -34,6 +35,7 @@ hook useFragmentInternal(
|
|
34
35
|
);
|
35
36
|
}
|
36
37
|
// $FlowFixMe[react-rule-hook] - the condition is static
|
38
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
37
39
|
return useFragmentInternal_CURRENT(
|
38
40
|
fragmentNode,
|
39
41
|
fragmentRef,
|
@@ -26,6 +26,7 @@ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreT
|
|
26
26
|
|
27
27
|
const {getQueryResourceForEnvironment} = require('./QueryResource');
|
28
28
|
const useRelayEnvironment = require('./useRelayEnvironment');
|
29
|
+
const useRelayLoggingContext = require('./useRelayLoggingContext');
|
29
30
|
const invariant = require('invariant');
|
30
31
|
const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
|
31
32
|
const {
|
@@ -109,15 +110,21 @@ function getSuspendingLiveResolver(
|
|
109
110
|
function handlePotentialSnapshotErrorsForState(
|
110
111
|
environment: IEnvironment,
|
111
112
|
state: FragmentState,
|
113
|
+
loggingContext: mixed | void,
|
112
114
|
): void {
|
113
115
|
if (state.kind === 'singular') {
|
114
116
|
handlePotentialSnapshotErrors(
|
115
117
|
environment,
|
116
|
-
state.snapshot.
|
118
|
+
state.snapshot.fieldErrors,
|
119
|
+
loggingContext,
|
117
120
|
);
|
118
121
|
} else if (state.kind === 'plural') {
|
119
122
|
for (const snapshot of state.snapshots) {
|
120
|
-
handlePotentialSnapshotErrors(
|
123
|
+
handlePotentialSnapshotErrors(
|
124
|
+
environment,
|
125
|
+
snapshot.fieldErrors,
|
126
|
+
loggingContext,
|
127
|
+
);
|
121
128
|
}
|
122
129
|
}
|
123
130
|
}
|
@@ -153,7 +160,7 @@ function handleMissedUpdates(
|
|
153
160
|
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
154
161
|
seenRecords: currentSnapshot.seenRecords,
|
155
162
|
selector: currentSnapshot.selector,
|
156
|
-
|
163
|
+
fieldErrors: currentSnapshot.fieldErrors,
|
157
164
|
};
|
158
165
|
return [
|
159
166
|
updatedData !== state.snapshot.data,
|
@@ -177,7 +184,7 @@ function handleMissedUpdates(
|
|
177
184
|
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
178
185
|
seenRecords: currentSnapshot.seenRecords,
|
179
186
|
selector: currentSnapshot.selector,
|
180
|
-
|
187
|
+
fieldErrors: currentSnapshot.fieldErrors,
|
181
188
|
};
|
182
189
|
if (updatedData !== snapshot.data) {
|
183
190
|
didMissUpdates = true;
|
@@ -403,6 +410,12 @@ hook useFragmentInternal(
|
|
403
410
|
);
|
404
411
|
|
405
412
|
const environment = useRelayEnvironment();
|
413
|
+
let loggerContext;
|
414
|
+
if (RelayFeatureFlags.ENABLE_UI_CONTEXT_ON_RELAY_LOGGER) {
|
415
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
416
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
417
|
+
loggerContext = useRelayLoggingContext();
|
418
|
+
}
|
406
419
|
const [_state, setState] = useState<FragmentState>(() =>
|
407
420
|
getFragmentState(environment, fragmentSelector),
|
408
421
|
);
|
@@ -448,12 +461,16 @@ hook useFragmentInternal(
|
|
448
461
|
|
449
462
|
// Handle the queries for any missing client edges; this may suspend.
|
450
463
|
// FIXME handle client edges in parallel.
|
451
|
-
if (
|
464
|
+
if (
|
465
|
+
fragmentNode.metadata?.hasClientEdges === true ||
|
466
|
+
RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
|
467
|
+
) {
|
452
468
|
// The fragment is validated to be static (in useFragment) and hasClientEdges is
|
453
469
|
// a static (constant) property of the fragment. In practice, this effect will
|
454
470
|
// always or never run for a given invocation of this hook.
|
455
471
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
456
472
|
// $FlowFixMe[react-rule-hook]
|
473
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
457
474
|
const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
|
458
475
|
const missingClientEdges = getMissingClientEdges(state);
|
459
476
|
// eslint-disable-next-line no-shadow
|
@@ -485,6 +502,7 @@ hook useFragmentInternal(
|
|
485
502
|
// See above note
|
486
503
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
487
504
|
// $FlowFixMe[react-rule-hook]
|
505
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
488
506
|
useEffect(() => {
|
489
507
|
const QueryResource = getQueryResourceForEnvironment(environment);
|
490
508
|
if (clientEdgeQueries?.length) {
|
@@ -541,7 +559,7 @@ hook useFragmentInternal(
|
|
541
559
|
|
542
560
|
// Report required fields only if we're not suspending, since that means
|
543
561
|
// they're missing even though we are out of options for possibly fetching them:
|
544
|
-
handlePotentialSnapshotErrorsForState(environment, state);
|
562
|
+
handlePotentialSnapshotErrorsForState(environment, state, loggerContext);
|
545
563
|
|
546
564
|
const hasPendingStateChanges = useRef<boolean>(false);
|
547
565
|
|
@@ -594,6 +612,7 @@ hook useFragmentInternal(
|
|
594
612
|
const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
|
595
613
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
596
614
|
// $FlowFixMe[react-rule-hook]
|
615
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
597
616
|
data = useMemo(() => {
|
598
617
|
if (state.kind === 'bailout') {
|
599
618
|
// Bailout state can happen if the fragmentRef is a plural array that is empty or has no
|
@@ -645,6 +664,7 @@ hook useFragmentInternal(
|
|
645
664
|
if (__DEV__) {
|
646
665
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
647
666
|
// $FlowFixMe[react-rule-hook]
|
667
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
648
668
|
useDebugValue({fragment: fragmentNode.name, data});
|
649
669
|
}
|
650
670
|
|
@@ -26,6 +26,7 @@ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreT
|
|
26
26
|
|
27
27
|
const {getQueryResourceForEnvironment} = require('./QueryResource');
|
28
28
|
const useRelayEnvironment = require('./useRelayEnvironment');
|
29
|
+
const useRelayLoggingContext = require('./useRelayLoggingContext');
|
29
30
|
const invariant = require('invariant');
|
30
31
|
const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
|
31
32
|
const {
|
@@ -121,15 +122,21 @@ function getSuspendingLiveResolver(
|
|
121
122
|
function handlePotentialSnapshotErrorsForState(
|
122
123
|
environment: IEnvironment,
|
123
124
|
state: FragmentState,
|
125
|
+
loggingContext: mixed | void,
|
124
126
|
): void {
|
125
127
|
if (state.kind === 'singular') {
|
126
128
|
handlePotentialSnapshotErrors(
|
127
129
|
environment,
|
128
|
-
state.snapshot.
|
130
|
+
state.snapshot.fieldErrors,
|
131
|
+
loggingContext,
|
129
132
|
);
|
130
133
|
} else if (state.kind === 'plural') {
|
131
134
|
for (const snapshot of state.snapshots) {
|
132
|
-
handlePotentialSnapshotErrors(
|
135
|
+
handlePotentialSnapshotErrors(
|
136
|
+
environment,
|
137
|
+
snapshot.fieldErrors,
|
138
|
+
loggingContext,
|
139
|
+
);
|
133
140
|
}
|
134
141
|
}
|
135
142
|
}
|
@@ -165,7 +172,7 @@ function handleMissedUpdates(
|
|
165
172
|
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
166
173
|
seenRecords: currentSnapshot.seenRecords,
|
167
174
|
selector: currentSnapshot.selector,
|
168
|
-
|
175
|
+
fieldErrors: currentSnapshot.fieldErrors,
|
169
176
|
};
|
170
177
|
return [
|
171
178
|
updatedData !== state.snapshot.data,
|
@@ -191,7 +198,7 @@ function handleMissedUpdates(
|
|
191
198
|
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
192
199
|
seenRecords: currentSnapshot.seenRecords,
|
193
200
|
selector: currentSnapshot.selector,
|
194
|
-
|
201
|
+
fieldErrors: currentSnapshot.fieldErrors,
|
195
202
|
};
|
196
203
|
if (updatedData !== snapshot.data) {
|
197
204
|
didMissUpdates = true;
|
@@ -215,13 +222,15 @@ function handleMissedUpdates(
|
|
215
222
|
}
|
216
223
|
}
|
217
224
|
|
225
|
+
type PromiseWithDisplayName = Promise<mixed> & {displayName?: string};
|
226
|
+
|
218
227
|
function handleMissingClientEdge(
|
219
228
|
environment: IEnvironment,
|
220
229
|
parentFragmentNode: ReaderFragment,
|
221
230
|
parentFragmentRef: mixed,
|
222
231
|
missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
|
223
232
|
queryOptions?: FragmentQueryOptions,
|
224
|
-
): [QueryResult, ?
|
233
|
+
): [QueryResult, ?PromiseWithDisplayName] {
|
225
234
|
const originalVariables = getVariablesFromFragment(
|
226
235
|
parentFragmentNode,
|
227
236
|
parentFragmentRef,
|
@@ -246,10 +255,17 @@ function handleMissingClientEdge(
|
|
246
255
|
queryOptions?.fetchPolicy,
|
247
256
|
);
|
248
257
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
258
|
+
const promise = getPromiseForActiveRequest(
|
259
|
+
environment,
|
260
|
+
queryOperationDescriptor.request,
|
261
|
+
);
|
262
|
+
// $FlowExpectedError[prop-missing]
|
263
|
+
if (promise != null && promise.displayName == null) {
|
264
|
+
// $FlowExpectedError[prop-missing]
|
265
|
+
promise.displayName = missingClientEdgeRequestInfo.request.params.name;
|
266
|
+
}
|
267
|
+
// $FlowFixMe[incompatible-exact] - Intentionally bypassing exactness check
|
268
|
+
return [queryResult, promise];
|
253
269
|
}
|
254
270
|
|
255
271
|
function subscribeToSnapshot(
|
@@ -431,6 +447,12 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
431
447
|
);
|
432
448
|
|
433
449
|
const environment = useRelayEnvironment();
|
450
|
+
let loggerContext;
|
451
|
+
if (RelayFeatureFlags.ENABLE_UI_CONTEXT_ON_RELAY_LOGGER) {
|
452
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
453
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
454
|
+
loggerContext = useRelayLoggingContext();
|
455
|
+
}
|
434
456
|
const [_state, setState] = useState<FragmentState>(() =>
|
435
457
|
getFragmentState(environment, fragmentSelector),
|
436
458
|
);
|
@@ -463,17 +485,21 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
463
485
|
|
464
486
|
// Handle the queries for any missing client edges; this may suspend.
|
465
487
|
// FIXME handle client edges in parallel.
|
466
|
-
if (
|
488
|
+
if (
|
489
|
+
fragmentNode.metadata?.hasClientEdges === true ||
|
490
|
+
RelayFeatureFlags.CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES
|
491
|
+
) {
|
467
492
|
// The fragment is validated to be static (in useFragment) and hasClientEdges is
|
468
493
|
// a static (constant) property of the fragment. In practice, this effect will
|
469
494
|
// always or never run for a given invocation of this hook.
|
470
495
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
471
496
|
// $FlowFixMe[react-rule-hook]
|
497
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
472
498
|
const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
|
473
499
|
const missingClientEdges = getMissingClientEdges(state);
|
474
500
|
// eslint-disable-next-line no-shadow
|
475
501
|
let clientEdgeQueries;
|
476
|
-
const activeRequestPromises = [];
|
502
|
+
const activeRequestPromises: Array<PromiseWithDisplayName> = [];
|
477
503
|
if (missingClientEdges?.length) {
|
478
504
|
clientEdgeQueries = ([]: Array<QueryResult>);
|
479
505
|
for (const edge of missingClientEdges) {
|
@@ -494,12 +520,18 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
494
520
|
}, [state, environment, fragmentNode, fragmentRef, queryOptions]);
|
495
521
|
|
496
522
|
if (activeRequestPromises.length) {
|
497
|
-
|
523
|
+
const allPromises = Promise.all(activeRequestPromises);
|
524
|
+
// $FlowExpectedError[prop-missing] Expando to annotate Promises.
|
525
|
+
allPromises.displayName = `RelayClientEdge(${activeRequestPromises
|
526
|
+
.map(promise => promise.displayName)
|
527
|
+
.join(',')})`;
|
528
|
+
throw allPromises;
|
498
529
|
}
|
499
530
|
|
500
531
|
// See above note
|
501
532
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
502
533
|
// $FlowFixMe[react-rule-hook]
|
534
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
503
535
|
useEffect(() => {
|
504
536
|
const QueryResource = getQueryResourceForEnvironment(environment);
|
505
537
|
if (clientEdgeQueries?.length) {
|
@@ -520,12 +552,15 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
520
552
|
// Suspend if a Live Resolver within this fragment is in a suspended state:
|
521
553
|
const suspendingLiveResolvers = getSuspendingLiveResolver(state);
|
522
554
|
if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
|
523
|
-
|
555
|
+
const promise = Promise.all(
|
524
556
|
suspendingLiveResolvers.map(liveStateID => {
|
525
557
|
// $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
|
526
558
|
return environment.getStore().getLiveResolverPromise(liveStateID);
|
527
559
|
}),
|
528
560
|
);
|
561
|
+
// $FlowExpectedError[prop-missing] Expando to annotate Promises.
|
562
|
+
promise.displayName = 'RelayLiveResolver(' + fragmentNode.name + ')';
|
563
|
+
throw promise;
|
529
564
|
}
|
530
565
|
// Suspend if an active operation bears on this fragment, either the
|
531
566
|
// fragment's owner or some other mutation etc. that could affect it.
|
@@ -556,7 +591,7 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
556
591
|
|
557
592
|
// Report required fields only if we're not suspending, since that means
|
558
593
|
// they're missing even though we are out of options for possibly fetching them:
|
559
|
-
handlePotentialSnapshotErrorsForState(environment, state);
|
594
|
+
handlePotentialSnapshotErrorsForState(environment, state, loggerContext);
|
560
595
|
|
561
596
|
// We emulate CRUD effects using a ref and two effects:
|
562
597
|
// - The ref tracks the current state (including updates from the subscription)
|
@@ -577,6 +612,7 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
577
612
|
selector: ?ReaderSelector,
|
578
613
|
environment: IEnvironment,
|
579
614
|
} | null>(null);
|
615
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
580
616
|
useEffect(() => {
|
581
617
|
const storeSubscription = storeSubscriptionRef.current;
|
582
618
|
if (storeSubscription != null) {
|
@@ -629,6 +665,7 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
629
665
|
environment: state.environment,
|
630
666
|
};
|
631
667
|
}, [state]);
|
668
|
+
// $FlowFixMe[react-rule-hook] - the condition is static
|
632
669
|
useEffect(() => {
|
633
670
|
if (storeSubscriptionRef.current == null && state.kind !== 'bailout') {
|
634
671
|
const dispose = subscribeToSnapshot(state.environment, state, setState);
|
@@ -656,6 +693,7 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
656
693
|
const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
|
657
694
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
658
695
|
// $FlowFixMe[react-rule-hook]
|
696
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
659
697
|
data = useMemo(() => {
|
660
698
|
if (state.kind === 'bailout') {
|
661
699
|
// Bailout state can happen if the fragmentRef is a plural array that is empty or has no
|
@@ -707,6 +745,7 @@ hook useFragmentInternal_EXPERIMENTAL(
|
|
707
745
|
if (__DEV__) {
|
708
746
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
709
747
|
// $FlowFixMe[react-rule-hook]
|
748
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
710
749
|
useDebugValue({fragment: fragmentNode.name, data});
|
711
750
|
}
|
712
751
|
|
@@ -32,23 +32,72 @@ const {
|
|
32
32
|
export type UseLazyLoadQueryHookType = hook <TVariables: Variables, TData>(
|
33
33
|
gqlQuery: Query<TVariables, TData>,
|
34
34
|
variables: TVariables,
|
35
|
-
options?:
|
36
|
-
fetchKey?: string | number,
|
37
|
-
fetchPolicy?: FetchPolicy,
|
38
|
-
networkCacheConfig?: CacheConfig,
|
39
|
-
UNSTABLE_renderPolicy?: RenderPolicy,
|
40
|
-
},
|
35
|
+
options?: Options,
|
41
36
|
) => TData;
|
42
37
|
|
38
|
+
type Options = {
|
39
|
+
/**
|
40
|
+
* Determines if cached data should be used, and when to send a network request based on the cached data that is currently available in the Relay store (for more details, see our [Fetch Policies](../../guided-tour/reusing-cached-data/fetch-policies) and [Garbage Collection](../../guided-tour/reusing-cached-data/presence-of-data) guides):
|
41
|
+
* * "store-or-network": _*(default)*_ *will* reuse locally cached data and will *only* send a network request if any data for the query is missing. If the query is fully cached, a network request will *not* be made.
|
42
|
+
* * "store-and-network": *will* reuse locally cached data and will *always* send a network request, regardless of whether any data was missing from the local cache or not.
|
43
|
+
* * "network-only": *will* *not* reuse locally cached data, and will *always* send a network request to fetch the query, ignoring any data that might be locally cached in Relay.
|
44
|
+
* * "store-only": *will* *only* reuse locally cached data, and will *never* send a network request to fetch the query. In this case, the responsibility of fetching the query falls to the caller, but this policy could also be used to read and operate on data that is entirely [local](../../guided-tour/updating-data/local-data-updates).
|
45
|
+
*/
|
46
|
+
+fetchPolicy?: FetchPolicy,
|
47
|
+
/**
|
48
|
+
* A `fetchKey` can be passed to force a re-evaluation of the current query and variables when the component re-renders, even if the variables didn't change, or even if the component isn't remounted (similarly to how passing a different `key` to a React component will cause it to remount). If the `fetchKey` is different from the one used in the previous render, the current query will be re-evaluated against the store, and it might be refetched depending on the current `fetchPolicy` and the state of the cache.
|
49
|
+
*/
|
50
|
+
+fetchKey?: string | number,
|
51
|
+
/**
|
52
|
+
* Default value: `{force: true}`. Object containing cache config options for the *network layer*. Note that the network layer may contain an *additional* query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely (which is the default behavior), pass `{force: true}` as the value for this option.
|
53
|
+
*/
|
54
|
+
+networkCacheConfig?: CacheConfig,
|
55
|
+
/**
|
56
|
+
* Undocumented option.
|
57
|
+
*/
|
58
|
+
+UNSTABLE_renderPolicy?: RenderPolicy,
|
59
|
+
};
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Hook used to fetch a GraphQL query during render. This hook can trigger multiple nested or waterfalling round trips if used without caution, and waits until render to start a data fetch (when it can usually start a lot sooner than render), thereby degrading performance. Instead, prefer [`usePreloadedQuery`](../use-preloaded-query).
|
63
|
+
*
|
64
|
+
* @example
|
65
|
+
* const React = require('React');
|
66
|
+
*
|
67
|
+
* const {graphql, useLazyLoadQuery} = require('react-relay');
|
68
|
+
*
|
69
|
+
* function App() {
|
70
|
+
* const data = useLazyLoadQuery(
|
71
|
+
* graphql`
|
72
|
+
* query AppQuery($id: ID!) {
|
73
|
+
* user(id: $id) {
|
74
|
+
* name
|
75
|
+
* }
|
76
|
+
* }
|
77
|
+
* `,
|
78
|
+
* {id: 4},
|
79
|
+
* {fetchPolicy: 'store-or-network'},
|
80
|
+
* );
|
81
|
+
*
|
82
|
+
* return <h1>{data.user?.name}</h1>;
|
83
|
+
* }
|
84
|
+
*
|
85
|
+
* @returns - `data`: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
|
86
|
+
* - The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of `data` above is: `{| user: ?{| name: ?string |} |}`.
|
87
|
+
*/
|
43
88
|
hook useLazyLoadQuery<TVariables: Variables, TData>(
|
89
|
+
/**
|
90
|
+
* GraphQL query specified using a `graphql` template literal.
|
91
|
+
*/
|
44
92
|
gqlQuery: Query<TVariables, TData>,
|
93
|
+
/**
|
94
|
+
* Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.
|
95
|
+
*/
|
45
96
|
variables: TVariables,
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
UNSTABLE_renderPolicy?: RenderPolicy,
|
51
|
-
},
|
97
|
+
/**
|
98
|
+
* options object
|
99
|
+
*/
|
100
|
+
options?: Options,
|
52
101
|
): TData {
|
53
102
|
const environment = useRelayEnvironment();
|
54
103
|
|
@@ -71,4 +120,4 @@ hook useLazyLoadQuery<TVariables: Variables, TData>(
|
|
71
120
|
}
|
72
121
|
|
73
122
|
// $FlowFixMe[react-rule-hook-incompatible]
|
74
|
-
module.exports =
|
123
|
+
module.exports = useLazyLoadQuery;
|
@@ -78,7 +78,7 @@ hook useLazyLoadQueryNode<TQuery: OperationType>({
|
|
78
78
|
useEffect(() => {
|
79
79
|
return () => {
|
80
80
|
// Attempt to detect if the component was
|
81
|
-
// hidden (by Offscreen API), or fast refresh
|
81
|
+
// hidden (by Offscreen API), or fast refresh occurred;
|
82
82
|
// Only in these situations would the effect cleanup
|
83
83
|
// for "unmounting" run multiple times, so if
|
84
84
|
// we are ever able to read this ref with a value
|
@@ -67,9 +67,11 @@ hook useLoadMoreFunction<TVariables: Variables>(
|
|
67
67
|
): [LoadMoreFn<TVariables>, boolean, () => void] {
|
68
68
|
if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
|
69
69
|
// $FlowFixMe[react-rule-hook] - the condition is static
|
70
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
70
71
|
return useLoadMoreFunction_EXPERIMENTAL(args);
|
71
72
|
}
|
72
73
|
// $FlowFixMe[react-rule-hook] - the condition is static
|
74
|
+
// $FlowFixMe[react-rule-hook-conditional]
|
73
75
|
return useLoadMoreFunction_CURRENT(args);
|
74
76
|
}
|
75
77
|
|
@@ -138,6 +140,8 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
|
|
138
140
|
};
|
139
141
|
}, [disposeFetch]);
|
140
142
|
|
143
|
+
const isRequestInvalid = fragmentData == null || isParentQueryActive;
|
144
|
+
|
141
145
|
const loadMore = useCallback(
|
142
146
|
(
|
143
147
|
count: number,
|
@@ -166,11 +170,8 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
|
|
166
170
|
}
|
167
171
|
|
168
172
|
const fragmentSelector = getSelector(fragmentNode, fragmentRef);
|
169
|
-
|
170
|
-
|
171
|
-
fragmentData == null ||
|
172
|
-
isParentQueryActive
|
173
|
-
) {
|
173
|
+
|
174
|
+
if (isFetchingRef.current === true || isRequestInvalid) {
|
174
175
|
if (fragmentSelector == null) {
|
175
176
|
warning(
|
176
177
|
false,
|
@@ -271,8 +272,7 @@ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
|
|
271
272
|
disposeFetch,
|
272
273
|
completeFetch,
|
273
274
|
isFetchingRef,
|
274
|
-
|
275
|
-
fragmentData,
|
275
|
+
isRequestInvalid,
|
276
276
|
fragmentNode.name,
|
277
277
|
fragmentRef,
|
278
278
|
componentDisplayName,
|