react-relay 13.2.0 → 14.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/ReactRelayContext.js +1 -1
- package/ReactRelayFragmentContainer.js.flow +7 -4
- package/ReactRelayPaginationContainer.js.flow +13 -8
- package/ReactRelayQueryFetcher.js.flow +1 -0
- package/ReactRelayQueryRenderer.js.flow +6 -2
- package/ReactRelayRefetchContainer.js.flow +10 -3
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +2 -2
- package/buildReactRelayContainer.js.flow +2 -2
- package/hooks.js +1 -1
- package/index.js +1 -1
- package/legacy.js +1 -1
- package/lib/ReactRelayQueryFetcher.js +1 -0
- package/lib/ReactRelayQueryRenderer.js +0 -1
- package/lib/readContext.js +2 -1
- package/lib/relay-hooks/FragmentResource.js +52 -10
- package/lib/relay-hooks/HooksImplementation.js +29 -0
- package/lib/relay-hooks/MatchContainer.js +1 -0
- package/lib/relay-hooks/QueryResource.js +2 -1
- package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +203 -56
- package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +254 -109
- package/lib/relay-hooks/react-cache/useFragment_REACT_CACHE.js +51 -0
- package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +13 -2
- package/lib/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js +125 -0
- package/lib/relay-hooks/useFragment.js +15 -1
- package/lib/relay-hooks/useLazyLoadQuery.js +18 -2
- package/lib/relay-hooks/useMutation.js +4 -5
- package/lib/relay-hooks/usePreloadedQuery.js +18 -2
- 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 +2 -2
- package/react-relay-legacy.min.js +2 -2
- package/react-relay.js +2 -2
- package/react-relay.min.js +2 -2
- package/readContext.js.flow +1 -0
- package/relay-hooks/FragmentResource.js.flow +55 -9
- package/relay-hooks/HooksImplementation.js.flow +45 -0
- package/relay-hooks/MatchContainer.js.flow +8 -1
- package/relay-hooks/QueryResource.js.flow +4 -2
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +2 -2
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +2 -2
- package/relay-hooks/loadQuery.js.flow +2 -1
- package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +245 -64
- package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +242 -99
- package/relay-hooks/react-cache/useFragment_REACT_CACHE.js.flow +74 -0
- package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +10 -4
- package/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js.flow +153 -0
- package/relay-hooks/useFragment.js.flow +17 -10
- package/relay-hooks/useLazyLoadQuery.js.flow +38 -3
- package/relay-hooks/useMutation.js.flow +3 -3
- package/relay-hooks/usePreloadedQuery.js.flow +30 -2
- package/relay-hooks/useRefetchableFragmentNode.js.flow +26 -11
- package/relay-hooks/useSubscription.js.flow +14 -8
@@ -37,10 +37,11 @@ const {
|
|
37
37
|
handlePotentialSnapshotErrors,
|
38
38
|
recycleNodesInto,
|
39
39
|
} = require('relay-runtime');
|
40
|
+
const warning = require('warning');
|
40
41
|
|
41
42
|
type FragmentQueryOptions = {|
|
42
43
|
fetchPolicy?: FetchPolicy,
|
43
|
-
networkCacheConfig?: CacheConfig,
|
44
|
+
networkCacheConfig?: ?CacheConfig,
|
44
45
|
|};
|
45
46
|
|
46
47
|
type FragmentState = $ReadOnly<
|
@@ -50,6 +51,7 @@ type FragmentState = $ReadOnly<
|
|
50
51
|
>;
|
51
52
|
|
52
53
|
type StateUpdater<T> = (T | (T => T)) => void;
|
54
|
+
type StateUpdaterFunction<T> = ((T) => T) => void;
|
53
55
|
|
54
56
|
function isMissingData(state: FragmentState): boolean {
|
55
57
|
if (state.kind === 'bailout') {
|
@@ -103,17 +105,21 @@ function handlePotentialSnapshotErrorsForState(
|
|
103
105
|
}
|
104
106
|
}
|
105
107
|
|
108
|
+
/**
|
109
|
+
* Check for updates to the store that occurred concurrently with rendering the given `state` value,
|
110
|
+
* returning a new (updated) state if there were updates or null if there were no changes.
|
111
|
+
*/
|
106
112
|
function handleMissedUpdates(
|
107
113
|
environment: IEnvironment,
|
108
114
|
state: FragmentState,
|
109
|
-
|
110
|
-
): void {
|
115
|
+
): null | [/* has data changed */ boolean, FragmentState] {
|
111
116
|
if (state.kind === 'bailout') {
|
112
|
-
return;
|
117
|
+
return null;
|
113
118
|
}
|
119
|
+
// FIXME this is invalid if we've just switched environments.
|
114
120
|
const currentEpoch = environment.getStore().getEpoch();
|
115
121
|
if (currentEpoch === state.epoch) {
|
116
|
-
return;
|
122
|
+
return null;
|
117
123
|
}
|
118
124
|
// The store has updated since we rendered (without us being subscribed yet),
|
119
125
|
// so check for any updates to the data we're rendering:
|
@@ -123,42 +129,58 @@ function handleMissedUpdates(
|
|
123
129
|
state.snapshot.data,
|
124
130
|
currentSnapshot.data,
|
125
131
|
);
|
126
|
-
|
127
|
-
|
132
|
+
const updatedCurrentSnapshot: Snapshot = {
|
133
|
+
data: updatedData,
|
134
|
+
isMissingData: currentSnapshot.isMissingData,
|
135
|
+
missingClientEdges: currentSnapshot.missingClientEdges,
|
136
|
+
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
137
|
+
seenRecords: currentSnapshot.seenRecords,
|
138
|
+
selector: currentSnapshot.selector,
|
139
|
+
missingRequiredFields: currentSnapshot.missingRequiredFields,
|
140
|
+
relayResolverErrors: currentSnapshot.relayResolverErrors,
|
141
|
+
};
|
142
|
+
return [
|
143
|
+
updatedData !== state.snapshot.data,
|
144
|
+
{
|
128
145
|
kind: 'singular',
|
129
|
-
snapshot:
|
146
|
+
snapshot: updatedCurrentSnapshot,
|
130
147
|
epoch: currentEpoch,
|
131
|
-
}
|
132
|
-
|
148
|
+
},
|
149
|
+
];
|
133
150
|
} else {
|
134
|
-
let
|
151
|
+
let didMissUpdates = false;
|
152
|
+
const currentSnapshots = [];
|
135
153
|
for (let index = 0; index < state.snapshots.length; index++) {
|
136
154
|
const snapshot = state.snapshots[index];
|
137
155
|
const currentSnapshot = environment.lookup(snapshot.selector);
|
138
156
|
const updatedData = recycleNodesInto(snapshot.data, currentSnapshot.data);
|
157
|
+
const updatedCurrentSnapshot: Snapshot = {
|
158
|
+
data: updatedData,
|
159
|
+
isMissingData: currentSnapshot.isMissingData,
|
160
|
+
missingClientEdges: currentSnapshot.missingClientEdges,
|
161
|
+
missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
|
162
|
+
seenRecords: currentSnapshot.seenRecords,
|
163
|
+
selector: currentSnapshot.selector,
|
164
|
+
missingRequiredFields: currentSnapshot.missingRequiredFields,
|
165
|
+
relayResolverErrors: currentSnapshot.relayResolverErrors,
|
166
|
+
};
|
139
167
|
if (updatedData !== snapshot.data) {
|
140
|
-
|
141
|
-
updates === null ? new Array(state.snapshots.length) : updates;
|
142
|
-
updates[index] = snapshot;
|
168
|
+
didMissUpdates = true;
|
143
169
|
}
|
170
|
+
currentSnapshots.push(updatedCurrentSnapshot);
|
144
171
|
}
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
}
|
158
|
-
}
|
159
|
-
return {kind: 'plural', snapshots: updated, epoch: currentEpoch};
|
160
|
-
});
|
161
|
-
}
|
172
|
+
invariant(
|
173
|
+
currentSnapshots.length === state.snapshots.length,
|
174
|
+
'Expected same number of snapshots',
|
175
|
+
);
|
176
|
+
return [
|
177
|
+
didMissUpdates,
|
178
|
+
{
|
179
|
+
kind: 'plural',
|
180
|
+
snapshots: currentSnapshots,
|
181
|
+
epoch: currentEpoch,
|
182
|
+
},
|
183
|
+
];
|
162
184
|
}
|
163
185
|
}
|
164
186
|
|
@@ -168,7 +190,7 @@ function handleMissingClientEdge(
|
|
168
190
|
parentFragmentRef: mixed,
|
169
191
|
missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
|
170
192
|
queryOptions?: FragmentQueryOptions,
|
171
|
-
): void {
|
193
|
+
): () => () => void {
|
172
194
|
const originalVariables = getVariablesFromFragment(
|
173
195
|
parentFragmentNode,
|
174
196
|
parentFragmentRef,
|
@@ -186,27 +208,30 @@ function handleMissingClientEdge(
|
|
186
208
|
// doing here is started the query if needed and retaining and releasing it
|
187
209
|
// according to the component mount/suspense cycle; getQueryResultOrFetchQuery
|
188
210
|
// already handles this by itself.
|
189
|
-
getQueryResultOrFetchQuery(
|
211
|
+
const [_, effect] = getQueryResultOrFetchQuery(
|
190
212
|
environment,
|
191
213
|
queryOperationDescriptor,
|
192
|
-
|
214
|
+
{
|
215
|
+
fetchPolicy: queryOptions?.fetchPolicy,
|
216
|
+
},
|
193
217
|
);
|
218
|
+
return effect;
|
194
219
|
}
|
195
220
|
|
196
221
|
function subscribeToSnapshot(
|
197
222
|
environment: IEnvironment,
|
198
223
|
state: FragmentState,
|
199
|
-
setState:
|
224
|
+
setState: StateUpdaterFunction<FragmentState>,
|
200
225
|
): () => void {
|
201
226
|
if (state.kind === 'bailout') {
|
202
227
|
return () => {};
|
203
228
|
} else if (state.kind === 'singular') {
|
204
229
|
const disposable = environment.subscribe(state.snapshot, latestSnapshot => {
|
205
|
-
setState({
|
230
|
+
setState(_ => ({
|
206
231
|
kind: 'singular',
|
207
232
|
snapshot: latestSnapshot,
|
208
233
|
epoch: environment.getStore().getEpoch(),
|
209
|
-
});
|
234
|
+
}));
|
210
235
|
});
|
211
236
|
return () => {
|
212
237
|
disposable.dispose();
|
@@ -240,6 +265,7 @@ function subscribeToSnapshot(
|
|
240
265
|
function getFragmentState(
|
241
266
|
environment: IEnvironment,
|
242
267
|
fragmentSelector: ?ReaderSelector,
|
268
|
+
isPlural: boolean,
|
243
269
|
): FragmentState {
|
244
270
|
if (fragmentSelector == null) {
|
245
271
|
return {kind: 'bailout'};
|
@@ -265,16 +291,17 @@ function useFragmentInternal_REACT_CACHE(
|
|
265
291
|
hookDisplayName: string,
|
266
292
|
queryOptions?: FragmentQueryOptions,
|
267
293
|
fragmentKey?: string,
|
268
|
-
): {
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
294
|
+
): ?SelectorData | Array<?SelectorData> {
|
295
|
+
const fragmentSelector = useMemo(
|
296
|
+
() => getSelector(fragmentNode, fragmentRef),
|
297
|
+
[fragmentNode, fragmentRef],
|
298
|
+
);
|
299
|
+
|
300
|
+
const isPlural = fragmentNode?.metadata?.plural === true;
|
274
301
|
|
275
|
-
if (
|
302
|
+
if (isPlural) {
|
276
303
|
invariant(
|
277
|
-
Array.isArray(fragmentRef),
|
304
|
+
fragmentRef == null || Array.isArray(fragmentRef),
|
278
305
|
'Relay: Expected fragment pointer%s for fragment `%s` to be ' +
|
279
306
|
'an array, instead got `%s`. Remove `@relay(plural: true)` ' +
|
280
307
|
'from fragment `%s` to allow the prop to be an object.',
|
@@ -296,7 +323,9 @@ function useFragmentInternal_REACT_CACHE(
|
|
296
323
|
);
|
297
324
|
}
|
298
325
|
invariant(
|
299
|
-
fragmentRef == null ||
|
326
|
+
fragmentRef == null ||
|
327
|
+
(isPlural && Array.isArray(fragmentRef) && fragmentRef.length === 0) ||
|
328
|
+
fragmentSelector != null,
|
300
329
|
'Relay: Expected to receive an object where `...%s` was spread, ' +
|
301
330
|
'but the fragment reference was not found`. This is most ' +
|
302
331
|
'likely the result of:\n' +
|
@@ -317,38 +346,94 @@ function useFragmentInternal_REACT_CACHE(
|
|
317
346
|
|
318
347
|
const environment = useRelayEnvironment();
|
319
348
|
const [rawState, setState] = useState<FragmentState>(() =>
|
320
|
-
getFragmentState(environment, fragmentSelector),
|
349
|
+
getFragmentState(environment, fragmentSelector, isPlural),
|
321
350
|
);
|
351
|
+
// On second look this separate rawState may not be needed at all, it can just be
|
352
|
+
// put into getFragmentState. Exception: can we properly handle the case where the
|
353
|
+
// fragmentRef goes from non-null to null?
|
354
|
+
const stateFromRawState = (state: FragmentState) => {
|
355
|
+
if (fragmentRef == null) {
|
356
|
+
return {kind: 'bailout'};
|
357
|
+
} else if (state.kind === 'plural' && state.snapshots.length === 0) {
|
358
|
+
return {kind: 'bailout'};
|
359
|
+
} else {
|
360
|
+
return state;
|
361
|
+
}
|
362
|
+
};
|
363
|
+
let state = stateFromRawState(rawState);
|
364
|
+
|
365
|
+
// This copy of the state we only update when something requires us to
|
366
|
+
// unsubscribe and re-subscribe, namely a changed environment or
|
367
|
+
// fragment selector.
|
368
|
+
const [rawSubscribedState, setSubscribedState] = useState(state);
|
369
|
+
// FIXME since this is used as an effect dependency, it needs to be memoized.
|
370
|
+
let subscribedState = stateFromRawState(rawSubscribedState);
|
322
371
|
|
323
372
|
const [previousFragmentSelector, setPreviousFragmentSelector] =
|
324
373
|
useState(fragmentSelector);
|
325
|
-
|
374
|
+
const [previousEnvironment, setPreviousEnvironment] = useState(environment);
|
375
|
+
if (
|
376
|
+
!areEqualSelectors(fragmentSelector, previousFragmentSelector) ||
|
377
|
+
environment !== previousEnvironment
|
378
|
+
) {
|
379
|
+
// Enqueue setState to record the new selector and state
|
326
380
|
setPreviousFragmentSelector(fragmentSelector);
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
state =
|
381
|
+
setPreviousEnvironment(environment);
|
382
|
+
const newState = stateFromRawState(
|
383
|
+
getFragmentState(environment, fragmentSelector, isPlural),
|
384
|
+
);
|
385
|
+
setState(newState);
|
386
|
+
setSubscribedState(newState); // This causes us to form a new subscription
|
387
|
+
// But render with the latest state w/o waiting for the setState. Otherwise
|
388
|
+
// the component would render the wrong information temporarily (including
|
389
|
+
// possibly incorrectly triggering some warnings below).
|
390
|
+
state = newState;
|
391
|
+
subscribedState = newState;
|
337
392
|
}
|
338
393
|
|
339
394
|
// Handle the queries for any missing client edges; this may suspend.
|
340
395
|
// FIXME handle client edges in parallel.
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
)
|
351
|
-
|
396
|
+
if (fragmentNode.metadata?.hasClientEdges === true) {
|
397
|
+
// The fragment is validated to be static (in useFragment) and hasClientEdges is
|
398
|
+
// a static (constant) property of the fragment. In practice, this effect will
|
399
|
+
// always or never run for a given invocation of this hook.
|
400
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
401
|
+
const effects = useMemo(() => {
|
402
|
+
const missingClientEdges = getMissingClientEdges(state);
|
403
|
+
// eslint-disable-next-line no-shadow
|
404
|
+
let effects;
|
405
|
+
if (missingClientEdges?.length) {
|
406
|
+
effects = [];
|
407
|
+
for (const edge of missingClientEdges) {
|
408
|
+
effects.push(
|
409
|
+
handleMissingClientEdge(
|
410
|
+
environment,
|
411
|
+
fragmentNode,
|
412
|
+
fragmentRef,
|
413
|
+
edge,
|
414
|
+
queryOptions,
|
415
|
+
),
|
416
|
+
);
|
417
|
+
}
|
418
|
+
}
|
419
|
+
return effects;
|
420
|
+
}, [state, environment, fragmentNode, fragmentRef, queryOptions]);
|
421
|
+
|
422
|
+
// See above note
|
423
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
424
|
+
useEffect(() => {
|
425
|
+
if (effects?.length) {
|
426
|
+
const cleanups = [];
|
427
|
+
for (const effect of effects) {
|
428
|
+
cleanups.push(effect());
|
429
|
+
}
|
430
|
+
return () => {
|
431
|
+
for (const cleanup of cleanups) {
|
432
|
+
cleanup();
|
433
|
+
}
|
434
|
+
};
|
435
|
+
}
|
436
|
+
}, [effects]);
|
352
437
|
}
|
353
438
|
|
354
439
|
if (isMissingData(state)) {
|
@@ -372,45 +457,103 @@ function useFragmentInternal_REACT_CACHE(
|
|
372
457
|
handlePotentialSnapshotErrorsForState(environment, state);
|
373
458
|
}
|
374
459
|
|
375
|
-
// Subscriptions:
|
376
|
-
const isMountedRef = useRef(false);
|
377
|
-
const isListeningForUpdatesRef = useRef(true);
|
378
|
-
function enableStoreUpdates() {
|
379
|
-
isListeningForUpdatesRef.current = true;
|
380
|
-
handleMissedUpdates(environment, state, setState);
|
381
|
-
}
|
382
|
-
function disableStoreUpdates() {
|
383
|
-
isListeningForUpdatesRef.current = false;
|
384
|
-
}
|
385
460
|
useEffect(() => {
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
461
|
+
// Check for updates since the state was rendered
|
462
|
+
let currentState = subscribedState;
|
463
|
+
const updates = handleMissedUpdates(environment, subscribedState);
|
464
|
+
if (updates !== null) {
|
465
|
+
const [didMissUpdates, updatedState] = updates;
|
466
|
+
// TODO: didMissUpdates only checks for changes to snapshot data, but it's possible
|
467
|
+
// that other snapshot properties may have changed that should also trigger a re-render,
|
468
|
+
// such as changed missing resolver fields, missing client edges, etc.
|
469
|
+
// A potential alternative is for handleMissedUpdates() to recycle the entire state
|
470
|
+
// value, and return the new (recycled) state only if there was some change. In that
|
471
|
+
// case the code would always setState if something in the snapshot changed, in addition
|
472
|
+
// to using the latest snapshot to subscribe.
|
473
|
+
if (didMissUpdates) {
|
474
|
+
setState(updatedState);
|
475
|
+
}
|
476
|
+
currentState = updatedState;
|
390
477
|
}
|
391
|
-
return subscribeToSnapshot(environment,
|
392
|
-
|
478
|
+
return subscribeToSnapshot(environment, currentState, updater => {
|
479
|
+
setState(latestState => {
|
480
|
+
if (
|
481
|
+
latestState.snapshot?.selector !== currentState.snapshot?.selector
|
482
|
+
) {
|
483
|
+
// Ignore updates to the subscription if it's for a previous fragment selector
|
484
|
+
// than the latest one to be rendered. This can happen if the store is updated
|
485
|
+
// after we re-render with a new fragmentRef prop but before the effect fires
|
486
|
+
// in which we unsubscribe to the old one and subscribe to the new one.
|
487
|
+
// (NB: it's safe to compare the selectors by reference because the selector
|
488
|
+
// is recycled into new snapshots.)
|
489
|
+
return latestState;
|
490
|
+
} else {
|
491
|
+
return updater(latestState);
|
492
|
+
}
|
493
|
+
});
|
494
|
+
});
|
495
|
+
}, [environment, subscribedState]);
|
393
496
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
497
|
+
let data: ?SelectorData | Array<?SelectorData>;
|
498
|
+
if (isPlural) {
|
499
|
+
// Plural fragments require allocating an array of the snasphot data values,
|
500
|
+
// which has to be memoized to avoid triggering downstream re-renders.
|
501
|
+
//
|
502
|
+
// Note that isPlural is a constant property of the fragment and does not change
|
503
|
+
// for a particular useFragment invocation site
|
504
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
505
|
+
data = useMemo(() => {
|
506
|
+
if (state.kind === 'bailout') {
|
507
|
+
return [];
|
508
|
+
} else {
|
509
|
+
invariant(
|
510
|
+
state.kind === 'plural',
|
511
|
+
'Expected state to be plural because fragment is plural',
|
512
|
+
);
|
513
|
+
return state.snapshots.map(s => s.data);
|
514
|
+
}
|
515
|
+
}, [state]);
|
516
|
+
} else if (state.kind === 'bailout') {
|
517
|
+
// This case doesn't allocate a new object so it doesn't have to be memoized
|
518
|
+
data = null;
|
519
|
+
} else {
|
520
|
+
// This case doesn't allocate a new object so it doesn't have to be memoized
|
521
|
+
invariant(
|
522
|
+
state.kind === 'singular',
|
523
|
+
'Expected state to be singular because fragment is singular',
|
524
|
+
);
|
525
|
+
data = state.snapshot.data;
|
526
|
+
}
|
527
|
+
|
528
|
+
if (__DEV__) {
|
529
|
+
if (
|
530
|
+
fragmentRef != null &&
|
531
|
+
(data === undefined ||
|
532
|
+
(Array.isArray(data) &&
|
533
|
+
data.length > 0 &&
|
534
|
+
data.every(d => d === undefined)))
|
535
|
+
) {
|
536
|
+
warning(
|
537
|
+
false,
|
538
|
+
'Relay: Expected to have been able to read non-null data for ' +
|
539
|
+
'fragment `%s` declared in ' +
|
540
|
+
'`%s`, since fragment reference was non-null. ' +
|
541
|
+
"Make sure that that `%s`'s parent isn't " +
|
542
|
+
'holding on to and/or passing a fragment reference for data that ' +
|
543
|
+
'has been deleted.',
|
544
|
+
fragmentNode.name,
|
545
|
+
hookDisplayName,
|
546
|
+
hookDisplayName,
|
547
|
+
);
|
548
|
+
}
|
549
|
+
}
|
403
550
|
|
404
551
|
if (__DEV__) {
|
405
552
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
406
553
|
useDebugValue({fragment: fragmentNode.name, data});
|
407
554
|
}
|
408
555
|
|
409
|
-
return
|
410
|
-
data,
|
411
|
-
disableStoreUpdates,
|
412
|
-
enableStoreUpdates,
|
413
|
-
};
|
556
|
+
return data;
|
414
557
|
}
|
415
558
|
|
416
559
|
module.exports = useFragmentInternal_REACT_CACHE;
|
@@ -0,0 +1,74 @@
|
|
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
|
+
* @emails oncall+relay
|
8
|
+
* @flow strict-local
|
9
|
+
* @format
|
10
|
+
*/
|
11
|
+
|
12
|
+
// flowlint ambiguous-object-type:error
|
13
|
+
|
14
|
+
'use strict';
|
15
|
+
|
16
|
+
import type {Fragment, FragmentType, GraphQLTaggedNode} from 'relay-runtime';
|
17
|
+
|
18
|
+
const {useTrackLoadQueryInRender} = require('../loadQuery');
|
19
|
+
const useStaticFragmentNodeWarning = require('../useStaticFragmentNodeWarning');
|
20
|
+
const useFragmentInternal = require('./useFragmentInternal_REACT_CACHE');
|
21
|
+
const {useDebugValue} = require('react');
|
22
|
+
const {getFragment} = require('relay-runtime');
|
23
|
+
|
24
|
+
type HasSpread<TFragmentType> = {
|
25
|
+
+$fragmentSpreads: TFragmentType,
|
26
|
+
...
|
27
|
+
};
|
28
|
+
|
29
|
+
// if the key is non-nullable, return non-nullable value
|
30
|
+
declare function useFragment<TFragmentType: FragmentType, TData>(
|
31
|
+
fragment: Fragment<TFragmentType, TData>,
|
32
|
+
key: HasSpread<TFragmentType>,
|
33
|
+
): TData;
|
34
|
+
|
35
|
+
// if the key is nullable, return nullable value
|
36
|
+
declare function useFragment<TFragmentType: FragmentType, TData>(
|
37
|
+
fragment: Fragment<TFragmentType, TData>,
|
38
|
+
key: ?HasSpread<TFragmentType>,
|
39
|
+
): ?TData;
|
40
|
+
|
41
|
+
// if the key is a non-nullable array of keys, return non-nullable array
|
42
|
+
declare function useFragment<TFragmentType: FragmentType, TData>(
|
43
|
+
fragment: Fragment<TFragmentType, TData>,
|
44
|
+
key: $ReadOnlyArray<HasSpread<TFragmentType>>,
|
45
|
+
): TData;
|
46
|
+
|
47
|
+
// if the key is a nullable array of keys, return nullable array
|
48
|
+
declare function useFragment<TFragmentType: FragmentType, TData>(
|
49
|
+
fragment: Fragment<TFragmentType, TData>,
|
50
|
+
key: ?$ReadOnlyArray<HasSpread<TFragmentType>>,
|
51
|
+
): ?TData;
|
52
|
+
|
53
|
+
function useFragment(fragment: GraphQLTaggedNode, key: mixed): mixed {
|
54
|
+
// We need to use this hook in order to be able to track if
|
55
|
+
// loadQuery was called during render
|
56
|
+
useTrackLoadQueryInRender();
|
57
|
+
|
58
|
+
const fragmentNode = getFragment(fragment);
|
59
|
+
if (__DEV__) {
|
60
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
61
|
+
useStaticFragmentNodeWarning(
|
62
|
+
fragmentNode,
|
63
|
+
'first argument of useFragment()',
|
64
|
+
);
|
65
|
+
}
|
66
|
+
const data = useFragmentInternal(fragmentNode, key, 'useFragment()');
|
67
|
+
if (__DEV__) {
|
68
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
69
|
+
useDebugValue({fragment: fragmentNode.name, data});
|
70
|
+
}
|
71
|
+
return data;
|
72
|
+
}
|
73
|
+
|
74
|
+
module.exports = useFragment;
|
@@ -26,6 +26,7 @@ const useMemoOperationDescriptor = require('../useMemoOperationDescriptor');
|
|
26
26
|
const useRelayEnvironment = require('../useRelayEnvironment');
|
27
27
|
const getQueryResultOrFetchQuery = require('./getQueryResultOrFetchQuery_REACT_CACHE');
|
28
28
|
const useFragmentInternal = require('./useFragmentInternal_REACT_CACHE');
|
29
|
+
const {useEffect} = require('react');
|
29
30
|
|
30
31
|
function useLazyLoadQuery_REACT_CACHE<TVariables: Variables, TData>(
|
31
32
|
gqlQuery: Query<TVariables, TData>,
|
@@ -47,20 +48,25 @@ function useLazyLoadQuery_REACT_CACHE<TVariables: Variables, TData>(
|
|
47
48
|
);
|
48
49
|
|
49
50
|
// Get the query going if needed -- this may suspend.
|
50
|
-
const queryResult = getQueryResultOrFetchQuery(
|
51
|
+
const [queryResult, effect] = getQueryResultOrFetchQuery(
|
51
52
|
environment,
|
52
53
|
queryOperationDescriptor,
|
53
|
-
|
54
|
+
{
|
55
|
+
fetchPolicy: options?.fetchPolicy,
|
56
|
+
renderPolicy: options?.UNSTABLE_renderPolicy,
|
57
|
+
fetchKey: options?.fetchKey,
|
58
|
+
},
|
54
59
|
);
|
55
60
|
|
61
|
+
useEffect(effect);
|
62
|
+
|
56
63
|
// Read the query's root fragment -- this may suspend.
|
57
64
|
const {fragmentNode, fragmentRef} = queryResult;
|
58
|
-
|
59
65
|
// $FlowExpectedError[incompatible-return] Is this a fixable incompatible-return?
|
60
66
|
return useFragmentInternal(fragmentNode, fragmentRef, 'useLazyLoadQuery()', {
|
61
67
|
fetchPolicy: options?.fetchPolicy,
|
62
68
|
networkCacheConfig: options?.networkCacheConfig,
|
63
|
-
})
|
69
|
+
});
|
64
70
|
}
|
65
71
|
|
66
72
|
module.exports = useLazyLoadQuery_REACT_CACHE;
|