react-relay 17.0.0 → 18.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/ReactRelayContainerUtils.js.flow +2 -2
  2. package/ReactRelayContext.js +1 -1
  3. package/ReactRelayContext.js.flow +1 -1
  4. package/ReactRelayFragmentContainer.js.flow +2 -2
  5. package/ReactRelayPaginationContainer.js.flow +2 -2
  6. package/ReactRelayQueryRenderer.js.flow +1 -1
  7. package/ReactRelayQueryRendererContext.js.flow +1 -1
  8. package/ReactRelayRefetchContainer.js.flow +2 -2
  9. package/ReactRelayTypes.js.flow +45 -18
  10. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +2 -2
  11. package/buildReactRelayContainer.js.flow +6 -5
  12. package/hooks.js +1 -1
  13. package/index.js +1 -1
  14. package/legacy.js +1 -1
  15. package/lib/relay-hooks/getConnectionState.js +47 -0
  16. package/lib/relay-hooks/legacy/FragmentResource.js +2 -6
  17. package/lib/relay-hooks/loadEntryPoint.js +8 -5
  18. package/lib/relay-hooks/loadQuery.js +2 -14
  19. package/lib/relay-hooks/readFragmentInternal.js +2 -4
  20. package/lib/relay-hooks/useEntryPointLoader.js +5 -8
  21. package/lib/relay-hooks/useFragment.js +4 -7
  22. package/lib/relay-hooks/useFragmentInternal.js +6 -484
  23. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +477 -0
  24. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +499 -0
  25. package/lib/relay-hooks/useLazyLoadQuery.js +2 -5
  26. package/lib/relay-hooks/useLoadMoreFunction.js +10 -43
  27. package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +130 -0
  28. package/lib/relay-hooks/usePreloadedQuery.js +6 -9
  29. package/lib/relay-hooks/useQueryLoader.js +9 -3
  30. package/lib/relay-hooks/useQueryLoader_EXPERIMENTAL.js +120 -0
  31. package/multi-actor/ActorChange.js.flow +1 -1
  32. package/package.json +3 -3
  33. package/react-relay-hooks.js +2 -2
  34. package/react-relay-hooks.min.js +2 -2
  35. package/react-relay-legacy.js +1 -1
  36. package/react-relay-legacy.min.js +1 -1
  37. package/react-relay.js +2 -2
  38. package/react-relay.min.js +2 -2
  39. package/relay-hooks/EntryPointTypes.flow.js.flow +35 -12
  40. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +8 -4
  41. package/relay-hooks/MatchContainer.js.flow +1 -1
  42. package/relay-hooks/ProfilerContext.js.flow +1 -1
  43. package/relay-hooks/__flowtests__/EntryPointTypes/ExtractQueryTypes-flowtest.js.flow +43 -0
  44. package/relay-hooks/getConnectionState.js.flow +97 -0
  45. package/relay-hooks/legacy/FragmentResource.js.flow +2 -13
  46. package/relay-hooks/loadEntryPoint.js.flow +10 -4
  47. package/relay-hooks/loadQuery.js.flow +4 -28
  48. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +4 -1
  49. package/relay-hooks/readFragmentInternal.js.flow +1 -10
  50. package/relay-hooks/useEntryPointLoader.js.flow +3 -4
  51. package/relay-hooks/useFragment.js.flow +0 -5
  52. package/relay-hooks/useFragmentInternal.js.flow +19 -643
  53. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +656 -0
  54. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +718 -0
  55. package/relay-hooks/useLazyLoadQuery.js.flow +0 -5
  56. package/relay-hooks/useLoadMoreFunction.js.flow +14 -80
  57. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +280 -0
  58. package/relay-hooks/usePaginationFragment.js.flow +1 -1
  59. package/relay-hooks/usePreloadedQuery.js.flow +0 -5
  60. package/relay-hooks/useQueryLoader.js.flow +28 -5
  61. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +253 -0
@@ -11,659 +11,35 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {QueryResult} from './QueryResource';
15
- import type {
16
- CacheConfig,
17
- FetchPolicy,
18
- IEnvironment,
19
- ReaderFragment,
20
- ReaderSelector,
21
- SelectorData,
22
- Snapshot,
23
- } from 'relay-runtime';
24
- import type {
25
- MissingClientEdgeRequestInfo,
26
- MissingLiveResolverField,
27
- } from 'relay-runtime/store/RelayStoreTypes';
14
+ import type {FragmentQueryOptions} from './useFragmentInternal_EXPERIMENTAL';
15
+ import type {ReaderFragment, SelectorData} from 'relay-runtime';
28
16
 
29
- const {getQueryResourceForEnvironment} = require('./QueryResource');
30
- const useRelayEnvironment = require('./useRelayEnvironment');
31
- const invariant = require('invariant');
32
- const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
33
- const {
34
- __internal: {fetchQuery: fetchQueryInternal, getPromiseForActiveRequest},
35
- RelayFeatureFlags,
36
- areEqualSelectors,
37
- createOperationDescriptor,
38
- getPendingOperationsForFragment,
39
- getSelector,
40
- getVariablesFromFragment,
41
- handlePotentialSnapshotErrors,
42
- recycleNodesInto,
43
- } = require('relay-runtime');
44
- const warning = require('warning');
17
+ import useFragmentInternal_CURRENT from './useFragmentInternal_CURRENT';
18
+ import useFragmentInternal_EXPERIMENTAL from './useFragmentInternal_EXPERIMENTAL';
19
+ import {RelayFeatureFlags} from 'relay-runtime';
45
20
 
46
- type FragmentQueryOptions = {
47
- fetchPolicy?: FetchPolicy,
48
- networkCacheConfig?: ?CacheConfig,
49
- };
50
-
51
- type FragmentState = $ReadOnly<
52
- | {kind: 'bailout'}
53
- | {kind: 'singular', snapshot: Snapshot, epoch: number}
54
- | {kind: 'plural', snapshots: $ReadOnlyArray<Snapshot>, epoch: number},
55
- >;
56
-
57
- type StateUpdaterFunction<T> = ((T) => T) => void;
58
-
59
- function isMissingData(state: FragmentState): boolean {
60
- if (state.kind === 'bailout') {
61
- return false;
62
- } else if (state.kind === 'singular') {
63
- return state.snapshot.isMissingData;
64
- } else {
65
- return state.snapshots.some(s => s.isMissingData);
66
- }
67
- }
68
-
69
- function getMissingClientEdges(
70
- state: FragmentState,
71
- ): $ReadOnlyArray<MissingClientEdgeRequestInfo> | null {
72
- if (state.kind === 'bailout') {
73
- return null;
74
- } else if (state.kind === 'singular') {
75
- return state.snapshot.missingClientEdges ?? null;
76
- } else {
77
- let edges: null | Array<MissingClientEdgeRequestInfo> = null;
78
- for (const snapshot of state.snapshots) {
79
- if (snapshot.missingClientEdges) {
80
- edges = edges ?? [];
81
- for (const edge of snapshot.missingClientEdges) {
82
- edges.push(edge);
83
- }
84
- }
85
- }
86
- return edges;
87
- }
88
- }
89
-
90
- function getSuspendingLiveResolver(
91
- state: FragmentState,
92
- ): $ReadOnlyArray<MissingLiveResolverField> | null {
93
- if (state.kind === 'bailout') {
94
- return null;
95
- } else if (state.kind === 'singular') {
96
- return state.snapshot.missingLiveResolverFields ?? null;
97
- } else {
98
- let missingFields: null | Array<MissingLiveResolverField> = null;
99
- for (const snapshot of state.snapshots) {
100
- if (snapshot.missingLiveResolverFields) {
101
- missingFields = missingFields ?? [];
102
- for (const edge of snapshot.missingLiveResolverFields) {
103
- missingFields.push(edge);
104
- }
105
- }
106
- }
107
- return missingFields;
108
- }
109
- }
110
-
111
- function handlePotentialSnapshotErrorsForState(
112
- environment: IEnvironment,
113
- state: FragmentState,
114
- ): void {
115
- if (state.kind === 'singular') {
116
- handlePotentialSnapshotErrors(
117
- environment,
118
- state.snapshot.missingRequiredFields,
119
- state.snapshot.relayResolverErrors,
120
- state.snapshot.errorResponseFields,
121
- state.snapshot.selector.node.metadata?.throwOnFieldError ?? false,
122
- );
123
- } else if (state.kind === 'plural') {
124
- for (const snapshot of state.snapshots) {
125
- handlePotentialSnapshotErrors(
126
- environment,
127
- snapshot.missingRequiredFields,
128
- snapshot.relayResolverErrors,
129
- snapshot.errorResponseFields,
130
- snapshot.selector.node.metadata?.throwOnFieldError ?? false,
131
- );
132
- }
133
- }
134
- }
135
-
136
- /**
137
- * Check for updates to the store that occurred concurrently with rendering the given `state` value,
138
- * returning a new (updated) state if there were updates or null if there were no changes.
139
- */
140
- function handleMissedUpdates(
141
- environment: IEnvironment,
142
- state: FragmentState,
143
- ): null | [/* has data changed */ boolean, FragmentState] {
144
- if (state.kind === 'bailout') {
145
- return null;
146
- }
147
- // FIXME this is invalid if we've just switched environments.
148
- const currentEpoch = environment.getStore().getEpoch();
149
- if (currentEpoch === state.epoch) {
150
- return null;
151
- }
152
- // The store has updated since we rendered (without us being subscribed yet),
153
- // so check for any updates to the data we're rendering:
154
- if (state.kind === 'singular') {
155
- const currentSnapshot = environment.lookup(state.snapshot.selector);
156
- const updatedData = recycleNodesInto(
157
- state.snapshot.data,
158
- currentSnapshot.data,
159
- );
160
- const updatedCurrentSnapshot: Snapshot = {
161
- data: updatedData,
162
- isMissingData: currentSnapshot.isMissingData,
163
- missingClientEdges: currentSnapshot.missingClientEdges,
164
- missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
165
- seenRecords: currentSnapshot.seenRecords,
166
- selector: currentSnapshot.selector,
167
- missingRequiredFields: currentSnapshot.missingRequiredFields,
168
- relayResolverErrors: currentSnapshot.relayResolverErrors,
169
- errorResponseFields: currentSnapshot.errorResponseFields,
170
- };
171
- return [
172
- updatedData !== state.snapshot.data,
173
- {
174
- kind: 'singular',
175
- snapshot: updatedCurrentSnapshot,
176
- epoch: currentEpoch,
177
- },
178
- ];
179
- } else {
180
- let didMissUpdates = false;
181
- const currentSnapshots = [];
182
- for (let index = 0; index < state.snapshots.length; index++) {
183
- const snapshot = state.snapshots[index];
184
- const currentSnapshot = environment.lookup(snapshot.selector);
185
- const updatedData = recycleNodesInto(snapshot.data, currentSnapshot.data);
186
- const updatedCurrentSnapshot: Snapshot = {
187
- data: updatedData,
188
- isMissingData: currentSnapshot.isMissingData,
189
- missingClientEdges: currentSnapshot.missingClientEdges,
190
- missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
191
- seenRecords: currentSnapshot.seenRecords,
192
- selector: currentSnapshot.selector,
193
- missingRequiredFields: currentSnapshot.missingRequiredFields,
194
- relayResolverErrors: currentSnapshot.relayResolverErrors,
195
- errorResponseFields: currentSnapshot.errorResponseFields,
196
- };
197
- if (updatedData !== snapshot.data) {
198
- didMissUpdates = true;
199
- }
200
- currentSnapshots.push(updatedCurrentSnapshot);
201
- }
202
- invariant(
203
- currentSnapshots.length === state.snapshots.length,
204
- 'Expected same number of snapshots',
205
- );
206
- return [
207
- didMissUpdates,
208
- {
209
- kind: 'plural',
210
- snapshots: currentSnapshots,
211
- epoch: currentEpoch,
212
- },
213
- ];
214
- }
215
- }
216
-
217
- function handleMissingClientEdge(
218
- environment: IEnvironment,
219
- parentFragmentNode: ReaderFragment,
220
- parentFragmentRef: mixed,
221
- missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
222
- queryOptions?: FragmentQueryOptions,
223
- ): [QueryResult, ?Promise<mixed>] {
224
- const originalVariables = getVariablesFromFragment(
225
- parentFragmentNode,
226
- parentFragmentRef,
227
- );
228
- const variables = {
229
- ...originalVariables,
230
- id: missingClientEdgeRequestInfo.clientEdgeDestinationID, // TODO should be a reserved name
231
- };
232
- const queryOperationDescriptor = createOperationDescriptor(
233
- missingClientEdgeRequestInfo.request,
234
- variables,
235
- queryOptions?.networkCacheConfig,
236
- );
237
- // This may suspend. We don't need to do anything with the results; all we're
238
- // doing here is started the query if needed and retaining and releasing it
239
- // according to the component mount/suspense cycle; QueryResource
240
- // already handles this by itself.
241
- const QueryResource = getQueryResourceForEnvironment(environment);
242
- const queryResult = QueryResource.prepare(
243
- queryOperationDescriptor,
244
- fetchQueryInternal(environment, queryOperationDescriptor),
245
- queryOptions?.fetchPolicy,
246
- );
247
-
248
- return [
249
- queryResult,
250
- getPromiseForActiveRequest(environment, queryOperationDescriptor.request),
251
- ];
252
- }
253
-
254
- function subscribeToSnapshot(
255
- environment: IEnvironment,
256
- state: FragmentState,
257
- setState: StateUpdaterFunction<FragmentState>,
258
- hasPendingStateChanges: {current: boolean},
259
- ): () => void {
260
- if (state.kind === 'bailout') {
261
- return () => {};
262
- } else if (state.kind === 'singular') {
263
- const disposable = environment.subscribe(state.snapshot, latestSnapshot => {
264
- setState(prevState => {
265
- // In theory a setState from a subscription could be batched together
266
- // with a setState to change the fragment selector. Guard against this
267
- // by bailing out of the state update if the selector has changed.
268
- if (
269
- prevState.kind !== 'singular' ||
270
- prevState.snapshot.selector !== latestSnapshot.selector
271
- ) {
272
- const updates = handleMissedUpdates(environment, prevState);
273
- if (updates != null) {
274
- const [dataChanged, nextState] = updates;
275
- environment.__log({
276
- name: 'useFragment.subscription.missedUpdates',
277
- hasDataChanges: dataChanged,
278
- });
279
- hasPendingStateChanges.current = dataChanged;
280
- return dataChanged ? nextState : prevState;
281
- } else {
282
- return prevState;
283
- }
284
- }
285
-
286
- hasPendingStateChanges.current = true;
287
- return {
288
- kind: 'singular',
289
- snapshot: latestSnapshot,
290
- epoch: environment.getStore().getEpoch(),
291
- };
292
- });
293
- });
294
- return () => {
295
- disposable.dispose();
296
- };
297
- } else {
298
- const disposables = state.snapshots.map((snapshot, index) =>
299
- environment.subscribe(snapshot, latestSnapshot => {
300
- setState(prevState => {
301
- // In theory a setState from a subscription could be batched together
302
- // with a setState to change the fragment selector. Guard against this
303
- // by bailing out of the state update if the selector has changed.
304
- if (
305
- prevState.kind !== 'plural' ||
306
- prevState.snapshots[index]?.selector !== latestSnapshot.selector
307
- ) {
308
- const updates = handleMissedUpdates(environment, prevState);
309
- if (updates != null) {
310
- const [dataChanged, nextState] = updates;
311
- environment.__log({
312
- name: 'useFragment.subscription.missedUpdates',
313
- hasDataChanges: dataChanged,
314
- });
315
- hasPendingStateChanges.current =
316
- hasPendingStateChanges.current || dataChanged;
317
- return dataChanged ? nextState : prevState;
318
- } else {
319
- return prevState;
320
- }
321
- }
322
- const updated = [...prevState.snapshots];
323
- updated[index] = latestSnapshot;
324
- hasPendingStateChanges.current = true;
325
- return {
326
- kind: 'plural',
327
- snapshots: updated,
328
- epoch: environment.getStore().getEpoch(),
329
- };
330
- });
331
- }),
332
- );
333
- return () => {
334
- for (const d of disposables) {
335
- d.dispose();
336
- }
337
- };
338
- }
339
- }
340
-
341
- function getFragmentState(
342
- environment: IEnvironment,
343
- fragmentSelector: ?ReaderSelector,
344
- ): FragmentState {
345
- if (fragmentSelector == null) {
346
- return {kind: 'bailout'};
347
- } else if (fragmentSelector.kind === 'PluralReaderSelector') {
348
- // Note that if fragmentRef is an empty array, fragmentSelector will be null so we'll hit the above case.
349
- // Null is returned by getSelector if fragmentRef has no non-null items.
350
- return {
351
- kind: 'plural',
352
- snapshots: fragmentSelector.selectors.map(s => environment.lookup(s)),
353
- epoch: environment.getStore().getEpoch(),
354
- };
355
- } else {
356
- return {
357
- kind: 'singular',
358
- snapshot: environment.lookup(fragmentSelector),
359
- epoch: environment.getStore().getEpoch(),
360
- };
361
- }
362
- }
363
-
364
- // fragmentNode cannot change during the lifetime of the component, though fragmentRef may change.
365
21
  hook useFragmentInternal(
366
22
  fragmentNode: ReaderFragment,
367
23
  fragmentRef: mixed,
368
24
  hookDisplayName: string,
369
25
  queryOptions?: FragmentQueryOptions,
370
26
  ): ?SelectorData | Array<?SelectorData> {
371
- const fragmentSelector = useMemo(
372
- () => getSelector(fragmentNode, fragmentRef),
373
- [fragmentNode, fragmentRef],
374
- );
375
-
376
- const isPlural = fragmentNode?.metadata?.plural === true;
377
-
378
- if (isPlural) {
379
- invariant(
380
- fragmentRef == null || Array.isArray(fragmentRef),
381
- 'Relay: Expected fragment pointer%s for fragment `%s` to be ' +
382
- 'an array, instead got `%s`. Remove `@relay(plural: true)` ' +
383
- 'from fragment `%s` to allow the prop to be an object.',
384
- fragmentNode.name,
385
- typeof fragmentRef,
386
- fragmentNode.name,
387
- );
388
- } else {
389
- invariant(
390
- !Array.isArray(fragmentRef),
391
- 'Relay: Expected fragment pointer%s for fragment `%s` not to be ' +
392
- 'an array, instead got `%s`. Add `@relay(plural: true)` ' +
393
- 'to fragment `%s` to allow the prop to be an array.',
394
- fragmentNode.name,
395
- typeof fragmentRef,
396
- fragmentNode.name,
397
- );
398
- }
399
- invariant(
400
- fragmentRef == null ||
401
- (isPlural && Array.isArray(fragmentRef) && fragmentRef.length === 0) ||
402
- fragmentSelector != null,
403
- 'Relay: Expected to receive an object where `...%s` was spread, ' +
404
- 'but the fragment reference was not found`. This is most ' +
405
- 'likely the result of:\n' +
406
- "- Forgetting to spread `%s` in `%s`'s parent's fragment.\n" +
407
- '- Conditionally fetching `%s` but unconditionally passing %s prop ' +
408
- 'to `%s`. If the parent fragment only fetches the fragment conditionally ' +
409
- '- with e.g. `@include`, `@skip`, or inside a `... on SomeType { }` ' +
410
- 'spread - then the fragment reference will not exist. ' +
411
- 'In this case, pass `null` if the conditions for evaluating the ' +
412
- 'fragment are not met (e.g. if the `@include(if)` value is false.)',
413
- fragmentNode.name,
414
- fragmentNode.name,
27
+ if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
28
+ // $FlowFixMe[react-rule-hook] - the condition is static
29
+ return useFragmentInternal_EXPERIMENTAL(
30
+ fragmentNode,
31
+ fragmentRef,
32
+ hookDisplayName,
33
+ queryOptions,
34
+ );
35
+ }
36
+ // $FlowFixMe[react-rule-hook] - the condition is static
37
+ return useFragmentInternal_CURRENT(
38
+ fragmentNode,
39
+ fragmentRef,
415
40
  hookDisplayName,
416
- fragmentNode.name,
417
- hookDisplayName,
418
- );
419
-
420
- const environment = useRelayEnvironment();
421
- const [_state, setState] = useState<FragmentState>(() =>
422
- getFragmentState(environment, fragmentSelector),
41
+ queryOptions,
423
42
  );
424
- let state = _state;
425
-
426
- // This copy of the state we only update when something requires us to
427
- // unsubscribe and re-subscribe, namely a changed environment or
428
- // fragment selector.
429
- const [_subscribedState, setSubscribedState] = useState(state);
430
- // FIXME since this is used as an effect dependency, it needs to be memoized.
431
- let subscribedState = _subscribedState;
432
-
433
- const [previousFragmentSelector, setPreviousFragmentSelector] =
434
- useState(fragmentSelector);
435
- const [previousEnvironment, setPreviousEnvironment] = useState(environment);
436
- if (
437
- !areEqualSelectors(fragmentSelector, previousFragmentSelector) ||
438
- environment !== previousEnvironment
439
- ) {
440
- // Enqueue setState to record the new selector and state
441
- setPreviousFragmentSelector(fragmentSelector);
442
- setPreviousEnvironment(environment);
443
- const newState = getFragmentState(environment, fragmentSelector);
444
- setState(newState);
445
- setSubscribedState(newState); // This causes us to form a new subscription
446
- // But render with the latest state w/o waiting for the setState. Otherwise
447
- // the component would render the wrong information temporarily (including
448
- // possibly incorrectly triggering some warnings below).
449
- state = newState;
450
- subscribedState = newState;
451
- }
452
-
453
- // The purpose of this is to detect whether we have ever committed, because we
454
- // don't suspend on store updates, only when the component either is first trying
455
- // to mount or when the our selector changes. The selector change in particular is
456
- // how we suspend for pagination and refetch. Also, fragment selector can be null
457
- // or undefined, so we use false as a special value to distinguish from all fragment
458
- // selectors; false means that the component hasn't mounted yet.
459
- const committedFragmentSelectorRef = useRef<false | ?ReaderSelector>(false);
460
- useEffect(() => {
461
- committedFragmentSelectorRef.current = fragmentSelector;
462
- }, [fragmentSelector]);
463
-
464
- // Handle the queries for any missing client edges; this may suspend.
465
- // FIXME handle client edges in parallel.
466
- if (fragmentNode.metadata?.hasClientEdges === true) {
467
- // The fragment is validated to be static (in useFragment) and hasClientEdges is
468
- // a static (constant) property of the fragment. In practice, this effect will
469
- // always or never run for a given invocation of this hook.
470
- // eslint-disable-next-line react-hooks/rules-of-hooks
471
- // $FlowFixMe[react-rule-hook]
472
- const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
473
- const missingClientEdges = getMissingClientEdges(state);
474
- // eslint-disable-next-line no-shadow
475
- let clientEdgeQueries;
476
- const activeRequestPromises = [];
477
- if (missingClientEdges?.length) {
478
- clientEdgeQueries = ([]: Array<QueryResult>);
479
- for (const edge of missingClientEdges) {
480
- const [queryResult, requestPromise] = handleMissingClientEdge(
481
- environment,
482
- fragmentNode,
483
- fragmentRef,
484
- edge,
485
- queryOptions,
486
- );
487
- clientEdgeQueries.push(queryResult);
488
- if (requestPromise != null) {
489
- activeRequestPromises.push(requestPromise);
490
- }
491
- }
492
- }
493
- return [clientEdgeQueries, activeRequestPromises];
494
- }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
495
-
496
- if (activeRequestPromises.length) {
497
- throw Promise.all(activeRequestPromises);
498
- }
499
-
500
- // See above note
501
- // eslint-disable-next-line react-hooks/rules-of-hooks
502
- // $FlowFixMe[react-rule-hook]
503
- useEffect(() => {
504
- const QueryResource = getQueryResourceForEnvironment(environment);
505
- if (clientEdgeQueries?.length) {
506
- const disposables = [];
507
- for (const query of clientEdgeQueries) {
508
- disposables.push(QueryResource.retain(query));
509
- }
510
- return () => {
511
- for (const disposable of disposables) {
512
- disposable.dispose();
513
- }
514
- };
515
- }
516
- }, [environment, clientEdgeQueries]);
517
- }
518
-
519
- if (isMissingData(state)) {
520
- // Suspend if a Live Resolver within this fragment is in a suspended state:
521
- const suspendingLiveResolvers = getSuspendingLiveResolver(state);
522
- if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
523
- throw Promise.all(
524
- suspendingLiveResolvers.map(({liveStateID}) => {
525
- // $FlowFixMe[prop-missing] This is expected to be a LiveResolverStore
526
- return environment.getStore().getLiveResolverPromise(liveStateID);
527
- }),
528
- );
529
- }
530
- // Suspend if an active operation bears on this fragment, either the
531
- // fragment's owner or some other mutation etc. that could affect it.
532
- // We only suspend when the component is first trying to mount or changing
533
- // selectors, not if data becomes missing later:
534
- if (
535
- RelayFeatureFlags.ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE ||
536
- environment !== previousEnvironment ||
537
- !committedFragmentSelectorRef.current ||
538
- // $FlowFixMe[react-rule-unsafe-ref]
539
- !areEqualSelectors(committedFragmentSelectorRef.current, fragmentSelector)
540
- ) {
541
- invariant(fragmentSelector != null, 'refinement, see invariants above');
542
- const fragmentOwner =
543
- fragmentSelector.kind === 'PluralReaderSelector'
544
- ? fragmentSelector.selectors[0].owner
545
- : fragmentSelector.owner;
546
- const pendingOperationsResult = getPendingOperationsForFragment(
547
- environment,
548
- fragmentNode,
549
- fragmentOwner,
550
- );
551
- if (pendingOperationsResult) {
552
- throw pendingOperationsResult.promise;
553
- }
554
- }
555
- }
556
-
557
- // Report required fields only if we're not suspending, since that means
558
- // they're missing even though we are out of options for possibly fetching them:
559
- handlePotentialSnapshotErrorsForState(environment, state);
560
-
561
- const hasPendingStateChanges = useRef<boolean>(false);
562
-
563
- useEffect(() => {
564
- // Check for updates since the state was rendered
565
- let currentState = subscribedState;
566
- const updates = handleMissedUpdates(environment, subscribedState);
567
- if (updates !== null) {
568
- const [didMissUpdates, updatedState] = updates;
569
- // TODO: didMissUpdates only checks for changes to snapshot data, but it's possible
570
- // that other snapshot properties may have changed that should also trigger a re-render,
571
- // such as changed missing resolver fields, missing client edges, etc.
572
- // A potential alternative is for handleMissedUpdates() to recycle the entire state
573
- // value, and return the new (recycled) state only if there was some change. In that
574
- // case the code would always setState if something in the snapshot changed, in addition
575
- // to using the latest snapshot to subscribe.
576
- if (didMissUpdates) {
577
- setState(updatedState);
578
- }
579
- currentState = updatedState;
580
- }
581
- return subscribeToSnapshot(
582
- environment,
583
- currentState,
584
- setState,
585
- hasPendingStateChanges,
586
- );
587
- }, [environment, subscribedState]);
588
-
589
- if (hasPendingStateChanges.current) {
590
- const updates = handleMissedUpdates(environment, state);
591
- if (updates != null) {
592
- const [hasStateUpdates, updatedState] = updates;
593
- if (hasStateUpdates) {
594
- setState(updatedState);
595
- state = updatedState;
596
- }
597
- }
598
- // $FlowFixMe[react-rule-unsafe-ref]
599
- hasPendingStateChanges.current = false;
600
- }
601
-
602
- let data: ?SelectorData | Array<?SelectorData>;
603
- if (isPlural) {
604
- // Plural fragments require allocating an array of the snapshot data values,
605
- // which has to be memoized to avoid triggering downstream re-renders.
606
- //
607
- // Note that isPlural is a constant property of the fragment and does not change
608
- // for a particular useFragment invocation site
609
- const fragmentRefIsNullish = fragmentRef == null; // for less sensitive memoization
610
- // eslint-disable-next-line react-hooks/rules-of-hooks
611
- // $FlowFixMe[react-rule-hook]
612
- data = useMemo(() => {
613
- if (state.kind === 'bailout') {
614
- // Bailout state can happen if the fragmentRef is a plural array that is empty or has no
615
- // non-null entries. In that case, the compatible behavior is to return [] instead of null.
616
- return fragmentRefIsNullish ? null : [];
617
- } else {
618
- invariant(
619
- state.kind === 'plural',
620
- 'Expected state to be plural because fragment is plural',
621
- );
622
- return state.snapshots.map(s => s.data);
623
- }
624
- }, [state, fragmentRefIsNullish]);
625
- } else if (state.kind === 'bailout') {
626
- // This case doesn't allocate a new object so it doesn't have to be memoized
627
- data = null;
628
- } else {
629
- // This case doesn't allocate a new object so it doesn't have to be memoized
630
- invariant(
631
- state.kind === 'singular',
632
- 'Expected state to be singular because fragment is singular',
633
- );
634
- data = state.snapshot.data;
635
- }
636
-
637
- if (RelayFeatureFlags.LOG_MISSING_RECORDS_IN_PROD || __DEV__) {
638
- if (
639
- fragmentRef != null &&
640
- (data === undefined ||
641
- (Array.isArray(data) &&
642
- data.length > 0 &&
643
- data.every(d => d === undefined)))
644
- ) {
645
- warning(
646
- false,
647
- 'Relay: Expected to have been able to read non-null data for ' +
648
- 'fragment `%s` declared in ' +
649
- '`%s`, since fragment reference was non-null. ' +
650
- "Make sure that that `%s`'s parent isn't " +
651
- 'holding on to and/or passing a fragment reference for data that ' +
652
- 'has been deleted.',
653
- fragmentNode.name,
654
- hookDisplayName,
655
- hookDisplayName,
656
- );
657
- }
658
- }
659
-
660
- if (__DEV__) {
661
- // eslint-disable-next-line react-hooks/rules-of-hooks
662
- // $FlowFixMe[react-rule-hook]
663
- useDebugValue({fragment: fragmentNode.name, data});
664
- }
665
-
666
- return data;
667
43
  }
668
44
 
669
45
  module.exports = useFragmentInternal;