react-relay 16.1.0 → 16.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.
Files changed (41) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayTypes.js.flow +1 -0
  3. package/hooks.js +1 -1
  4. package/hooks.js.flow +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +1 -1
  7. package/legacy.js +1 -1
  8. package/lib/relay-hooks/SuspenseResource.js +7 -4
  9. package/lib/relay-hooks/experimental/readFragmentInternal_EXPERIMENTAL.js +2 -2
  10. package/lib/relay-hooks/experimental/useFragmentInternal_EXPERIMENTAL.js +61 -27
  11. package/lib/relay-hooks/{FragmentResource.js → legacy/FragmentResource.js} +7 -6
  12. package/lib/relay-hooks/{useBlockingPaginationFragment.js → legacy/useBlockingPaginationFragment.js} +2 -2
  13. package/lib/relay-hooks/{useFragmentNode.js → legacy/useFragmentNode.js} +2 -2
  14. package/lib/relay-hooks/{useRefetchableFragmentNode.js → legacy/useRefetchableFragmentNode.js} +8 -8
  15. package/lib/relay-hooks/useFragment.js +1 -1
  16. package/lib/relay-hooks/useLazyLoadQueryNode.js +1 -1
  17. package/lib/relay-hooks/usePaginationFragment.js +1 -1
  18. package/lib/relay-hooks/useRefetchableFragment.js +1 -1
  19. package/package.json +2 -2
  20. package/react-relay-hooks.js +2 -2
  21. package/react-relay-hooks.min.js +2 -2
  22. package/react-relay-legacy.js +1 -1
  23. package/react-relay-legacy.min.js +1 -1
  24. package/react-relay.js +2 -2
  25. package/react-relay.min.js +2 -2
  26. package/relay-hooks/EntryPointTypes.flow.js.flow +8 -14
  27. package/relay-hooks/NestedRelayEntryPointBuilderUtils.js.flow +5 -11
  28. package/relay-hooks/SuspenseResource.js.flow +11 -8
  29. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +1 -1
  30. package/relay-hooks/experimental/readFragmentInternal_EXPERIMENTAL.js.flow +2 -0
  31. package/relay-hooks/experimental/useFragmentInternal_EXPERIMENTAL.js.flow +57 -14
  32. package/relay-hooks/experimental/useFragment_EXPERIMENTAL.js.flow +9 -3
  33. package/relay-hooks/{FragmentResource.js.flow → legacy/FragmentResource.js.flow} +8 -5
  34. package/relay-hooks/{useBlockingPaginationFragment.js.flow → legacy/useBlockingPaginationFragment.js.flow} +4 -4
  35. package/relay-hooks/{useFragmentNode.js.flow → legacy/useFragmentNode.js.flow} +2 -2
  36. package/relay-hooks/{useRefetchableFragmentNode.js.flow → legacy/useRefetchableFragmentNode.js.flow} +7 -7
  37. package/relay-hooks/loadQuery.js.flow +1 -1
  38. package/relay-hooks/useFragment.js.flow +10 -4
  39. package/relay-hooks/useLazyLoadQueryNode.js.flow +1 -1
  40. package/relay-hooks/usePaginationFragment.js.flow +2 -2
  41. package/relay-hooks/useRefetchableFragment.js.flow +2 -2
@@ -31,7 +31,7 @@ const useRelayEnvironment = require('../useRelayEnvironment');
31
31
  const invariant = require('invariant');
32
32
  const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
33
33
  const {
34
- __internal: {fetchQuery: fetchQueryInternal},
34
+ __internal: {fetchQuery: fetchQueryInternal, getPromiseForActiveRequest},
35
35
  RelayFeatureFlags,
36
36
  areEqualSelectors,
37
37
  createOperationDescriptor,
@@ -117,6 +117,7 @@ function handlePotentialSnapshotErrorsForState(
117
117
  environment,
118
118
  state.snapshot.missingRequiredFields,
119
119
  state.snapshot.relayResolverErrors,
120
+ state.snapshot.errorResponseFields,
120
121
  );
121
122
  } else if (state.kind === 'plural') {
122
123
  for (const snapshot of state.snapshots) {
@@ -124,6 +125,7 @@ function handlePotentialSnapshotErrorsForState(
124
125
  environment,
125
126
  snapshot.missingRequiredFields,
126
127
  snapshot.relayResolverErrors,
128
+ snapshot.errorResponseFields,
127
129
  );
128
130
  }
129
131
  }
@@ -162,6 +164,7 @@ function handleMissedUpdates(
162
164
  selector: currentSnapshot.selector,
163
165
  missingRequiredFields: currentSnapshot.missingRequiredFields,
164
166
  relayResolverErrors: currentSnapshot.relayResolverErrors,
167
+ errorResponseFields: currentSnapshot.errorResponseFields,
165
168
  };
166
169
  return [
167
170
  updatedData !== state.snapshot.data,
@@ -187,6 +190,7 @@ function handleMissedUpdates(
187
190
  selector: currentSnapshot.selector,
188
191
  missingRequiredFields: currentSnapshot.missingRequiredFields,
189
192
  relayResolverErrors: currentSnapshot.relayResolverErrors,
193
+ errorResponseFields: currentSnapshot.errorResponseFields,
190
194
  };
191
195
  if (updatedData !== snapshot.data) {
192
196
  didMissUpdates = true;
@@ -214,7 +218,7 @@ function handleMissingClientEdge(
214
218
  parentFragmentRef: mixed,
215
219
  missingClientEdgeRequestInfo: MissingClientEdgeRequestInfo,
216
220
  queryOptions?: FragmentQueryOptions,
217
- ): QueryResult {
221
+ ): [QueryResult, ?Promise<mixed>] {
218
222
  const originalVariables = getVariablesFromFragment(
219
223
  parentFragmentNode,
220
224
  parentFragmentRef,
@@ -233,17 +237,23 @@ function handleMissingClientEdge(
233
237
  // according to the component mount/suspense cycle; QueryResource
234
238
  // already handles this by itself.
235
239
  const QueryResource = getQueryResourceForEnvironment(environment);
236
- return QueryResource.prepare(
240
+ const queryResult = QueryResource.prepare(
237
241
  queryOperationDescriptor,
238
242
  fetchQueryInternal(environment, queryOperationDescriptor),
239
243
  queryOptions?.fetchPolicy,
240
244
  );
245
+
246
+ return [
247
+ queryResult,
248
+ getPromiseForActiveRequest(environment, queryOperationDescriptor.request),
249
+ ];
241
250
  }
242
251
 
243
252
  function subscribeToSnapshot(
244
253
  environment: IEnvironment,
245
254
  state: FragmentState,
246
255
  setState: StateUpdaterFunction<FragmentState>,
256
+ hasPendingStateChanges: {current: boolean},
247
257
  ): () => void {
248
258
  if (state.kind === 'bailout') {
249
259
  return () => {};
@@ -264,11 +274,14 @@ function subscribeToSnapshot(
264
274
  name: 'useFragment.subscription.missedUpdates',
265
275
  hasDataChanges: dataChanged,
266
276
  });
277
+ hasPendingStateChanges.current = dataChanged;
267
278
  return dataChanged ? nextState : prevState;
268
279
  } else {
269
280
  return prevState;
270
281
  }
271
282
  }
283
+
284
+ hasPendingStateChanges.current = true;
272
285
  return {
273
286
  kind: 'singular',
274
287
  snapshot: latestSnapshot,
@@ -297,6 +310,8 @@ function subscribeToSnapshot(
297
310
  name: 'useFragment.subscription.missedUpdates',
298
311
  hasDataChanges: dataChanged,
299
312
  });
313
+ hasPendingStateChanges.current =
314
+ hasPendingStateChanges.current || dataChanged;
300
315
  return dataChanged ? nextState : prevState;
301
316
  } else {
302
317
  return prevState;
@@ -304,6 +319,7 @@ function subscribeToSnapshot(
304
319
  }
305
320
  const updated = [...prevState.snapshots];
306
321
  updated[index] = latestSnapshot;
322
+ hasPendingStateChanges.current = true;
307
323
  return {
308
324
  kind: 'plural',
309
325
  snapshots: updated,
@@ -450,27 +466,34 @@ function useFragmentInternal_EXPERIMENTAL(
450
466
  // a static (constant) property of the fragment. In practice, this effect will
451
467
  // always or never run for a given invocation of this hook.
452
468
  // eslint-disable-next-line react-hooks/rules-of-hooks
453
- const clientEdgeQueries = useMemo(() => {
469
+ const [clientEdgeQueries, activeRequestPromises] = useMemo(() => {
454
470
  const missingClientEdges = getMissingClientEdges(state);
455
471
  // eslint-disable-next-line no-shadow
456
472
  let clientEdgeQueries;
473
+ const activeRequestPromises = [];
457
474
  if (missingClientEdges?.length) {
458
475
  clientEdgeQueries = ([]: Array<QueryResult>);
459
476
  for (const edge of missingClientEdges) {
460
- clientEdgeQueries.push(
461
- handleMissingClientEdge(
462
- environment,
463
- fragmentNode,
464
- fragmentRef,
465
- edge,
466
- queryOptions,
467
- ),
477
+ const [queryResult, requestPromise] = handleMissingClientEdge(
478
+ environment,
479
+ fragmentNode,
480
+ fragmentRef,
481
+ edge,
482
+ queryOptions,
468
483
  );
484
+ clientEdgeQueries.push(queryResult);
485
+ if (requestPromise != null) {
486
+ activeRequestPromises.push(requestPromise);
487
+ }
469
488
  }
470
489
  }
471
- return clientEdgeQueries;
490
+ return [clientEdgeQueries, activeRequestPromises];
472
491
  }, [state, environment, fragmentNode, fragmentRef, queryOptions]);
473
492
 
493
+ if (activeRequestPromises.length) {
494
+ throw Promise.all(activeRequestPromises);
495
+ }
496
+
474
497
  // See above note
475
498
  // eslint-disable-next-line react-hooks/rules-of-hooks
476
499
  useEffect(() => {
@@ -505,6 +528,7 @@ function useFragmentInternal_EXPERIMENTAL(
505
528
  // We only suspend when the component is first trying to mount or changing
506
529
  // selectors, not if data becomes missing later:
507
530
  if (
531
+ environment !== previousEnvironment ||
508
532
  !committedFragmentSelectorRef.current ||
509
533
  !areEqualSelectors(committedFragmentSelectorRef.current, fragmentSelector)
510
534
  ) {
@@ -528,6 +552,8 @@ function useFragmentInternal_EXPERIMENTAL(
528
552
  // they're missing even though we are out of options for possibly fetching them:
529
553
  handlePotentialSnapshotErrorsForState(environment, state);
530
554
 
555
+ const hasPendingStateChanges = useRef<boolean>(false);
556
+
531
557
  useEffect(() => {
532
558
  // Check for updates since the state was rendered
533
559
  let currentState = subscribedState;
@@ -546,9 +572,26 @@ function useFragmentInternal_EXPERIMENTAL(
546
572
  }
547
573
  currentState = updatedState;
548
574
  }
549
- return subscribeToSnapshot(environment, currentState, setState);
575
+ return subscribeToSnapshot(
576
+ environment,
577
+ currentState,
578
+ setState,
579
+ hasPendingStateChanges,
580
+ );
550
581
  }, [environment, subscribedState]);
551
582
 
583
+ if (hasPendingStateChanges.current) {
584
+ const updates = handleMissedUpdates(environment, state);
585
+ if (updates != null) {
586
+ const [hasStateUpdates, updatedState] = updates;
587
+ if (hasStateUpdates) {
588
+ setState(updatedState);
589
+ state = updatedState;
590
+ }
591
+ }
592
+ hasPendingStateChanges.current = false;
593
+ }
594
+
552
595
  let data: ?SelectorData | Array<?SelectorData>;
553
596
  if (isPlural) {
554
597
  // Plural fragments require allocating an array of the snapshot data values,
@@ -30,17 +30,23 @@ declare function useFragment<TFragmentType: FragmentType, TData>(
30
30
  key: HasSpread<TFragmentType>,
31
31
  ): TData;
32
32
 
33
+ // if the key is nullable, return nullable value
34
+ declare function useFragment<TFragmentType: FragmentType, TData>(
35
+ fragment: Fragment<TFragmentType, TData>,
36
+ key: ?HasSpread<TFragmentType>,
37
+ ): ?TData;
38
+
33
39
  // if the key is a non-nullable array of keys, return non-nullable array
34
40
  declare function useFragment<TFragmentType: FragmentType, TData>(
35
41
  fragment: Fragment<TFragmentType, TData>,
36
42
  key: $ReadOnlyArray<HasSpread<TFragmentType>>,
37
43
  ): TData;
38
44
 
39
- // if the key is null/void, return null/void value
45
+ // if the key is a nullable array of keys, return nullable array
40
46
  declare function useFragment<TFragmentType: FragmentType, TData>(
41
47
  fragment: Fragment<TFragmentType, TData>,
42
- key: null | void,
43
- ): null | void;
48
+ key: ?$ReadOnlyArray<HasSpread<TFragmentType>>,
49
+ ): ?TData;
44
50
 
45
51
  function useFragment(fragment: GraphQLTaggedNode, key: mixed): mixed {
46
52
  // We need to use this hook in order to be able to track if
@@ -11,8 +11,8 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {Cache} from './LRUCache';
15
- import type {QueryResource, QueryResult} from './QueryResource';
14
+ import type {Cache} from '../LRUCache';
15
+ import type {QueryResource, QueryResult} from '../QueryResource';
16
16
  import type {
17
17
  ConcreteRequest,
18
18
  DataID,
@@ -24,9 +24,9 @@ import type {
24
24
  } from 'relay-runtime';
25
25
  import type {MissingLiveResolverField} from 'relay-runtime/store/RelayStoreTypes';
26
26
 
27
- const LRUCache = require('./LRUCache');
28
- const {getQueryResourceForEnvironment} = require('./QueryResource');
29
- const SuspenseResource = require('./SuspenseResource');
27
+ const LRUCache = require('../LRUCache');
28
+ const {getQueryResourceForEnvironment} = require('../QueryResource');
29
+ const SuspenseResource = require('../SuspenseResource');
30
30
  const invariant = require('invariant');
31
31
  const {
32
32
  __internal: {fetchQuery, getPromiseForActiveRequest},
@@ -566,6 +566,7 @@ class FragmentResourceImpl {
566
566
  this._environment,
567
567
  s.missingRequiredFields,
568
568
  s.relayResolverErrors,
569
+ s.errorResponseFields,
569
570
  );
570
571
  });
571
572
  } else {
@@ -573,6 +574,7 @@ class FragmentResourceImpl {
573
574
  this._environment,
574
575
  snapshot.missingRequiredFields,
575
576
  snapshot.relayResolverErrors,
577
+ snapshot.errorResponseFields,
576
578
  );
577
579
  }
578
580
  }
@@ -776,6 +778,7 @@ class FragmentResourceImpl {
776
778
  selector: currentSnapshot.selector,
777
779
  missingRequiredFields: currentSnapshot.missingRequiredFields,
778
780
  relayResolverErrors: currentSnapshot.relayResolverErrors,
781
+ errorResponseFields: currentSnapshot.errorResponseFields,
779
782
  };
780
783
  if (updatedData !== renderData) {
781
784
  const result = getFragmentResult(
@@ -11,9 +11,9 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {RefetchableFragment} from '../../relay-runtime/util/RelayRuntimeTypes';
15
- import type {LoadMoreFn, UseLoadMoreFunctionArgs} from './useLoadMoreFunction';
14
+ import type {LoadMoreFn, UseLoadMoreFunctionArgs} from '../useLoadMoreFunction';
16
15
  import type {Options} from './useRefetchableFragmentNode';
16
+ import type {RefetchableFragment} from 'relay-runtime';
17
17
  import type {
18
18
  Disposable,
19
19
  FragmentType,
@@ -22,9 +22,9 @@ import type {
22
22
  Variables,
23
23
  } from 'relay-runtime';
24
24
 
25
- const useLoadMoreFunction = require('./useLoadMoreFunction');
25
+ const useLoadMoreFunction = require('../useLoadMoreFunction');
26
+ const useStaticFragmentNodeWarning = require('../useStaticFragmentNodeWarning');
26
27
  const useRefetchableFragmentNode = require('./useRefetchableFragmentNode');
27
- const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
28
28
  const invariant = require('invariant');
29
29
  const {useCallback, useEffect, useRef, useState} = require('react');
30
30
  const {
@@ -13,9 +13,9 @@
13
13
 
14
14
  import type {ReaderFragment} from 'relay-runtime';
15
15
 
16
+ const useRelayEnvironment = require('../useRelayEnvironment');
17
+ const useUnsafeRef_DEPRECATED = require('../useUnsafeRef_DEPRECATED');
16
18
  const {getFragmentResourceForEnvironment} = require('./FragmentResource');
17
- const useRelayEnvironment = require('./useRelayEnvironment');
18
- const useUnsafeRef_DEPRECATED = require('./useUnsafeRef_DEPRECATED');
19
19
  const {useEffect, useState} = require('react');
20
20
  const {RelayFeatureFlags, getFragmentIdentifier} = require('relay-runtime');
21
21
  const warning = require('warning');
@@ -11,8 +11,8 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {RefetchableIdentifierInfo} from '../../relay-runtime/util/ReaderNode';
15
- import type {LoaderFn} from './useQueryLoader';
14
+ import type {LoaderFn} from '../useQueryLoader';
15
+ import type {RefetchableIdentifierInfo} from 'relay-runtime';
16
16
  import type {
17
17
  ConcreteRequest,
18
18
  Disposable,
@@ -26,13 +26,13 @@ import type {
26
26
  VariablesOf,
27
27
  } from 'relay-runtime';
28
28
 
29
+ const ProfilerContext = require('../ProfilerContext');
30
+ const {getQueryResourceForEnvironment} = require('../QueryResource');
31
+ const useIsMountedRef = require('../useIsMountedRef');
32
+ const useQueryLoader = require('../useQueryLoader');
33
+ const useRelayEnvironment = require('../useRelayEnvironment');
29
34
  const {getFragmentResourceForEnvironment} = require('./FragmentResource');
30
- const ProfilerContext = require('./ProfilerContext');
31
- const {getQueryResourceForEnvironment} = require('./QueryResource');
32
35
  const useFragmentNode = require('./useFragmentNode');
33
- const useIsMountedRef = require('./useIsMountedRef');
34
- const useQueryLoader = require('./useQueryLoader');
35
- const useRelayEnvironment = require('./useRelayEnvironment');
36
36
  const invariant = require('invariant');
37
37
  const {useCallback, useContext, useReducer} = require('react');
38
38
  const {
@@ -62,7 +62,7 @@ type QueryType<T> = T extends Query<infer V, infer D, infer RR>
62
62
  variables: V,
63
63
  response: D,
64
64
  rawResponse?: $NonMaybeType<RR>,
65
- }
65
+ } // $FlowFixMe[deprecated-type]
66
66
  : $Call<<T>(PreloadableConcreteRequest<T>) => T, T>;
67
67
 
68
68
  declare function loadQuery<
@@ -14,8 +14,8 @@
14
14
  import type {Fragment, FragmentType, GraphQLTaggedNode} from 'relay-runtime';
15
15
 
16
16
  const HooksImplementation = require('./HooksImplementation');
17
+ const useFragmentNode = require('./legacy/useFragmentNode');
17
18
  const {useTrackLoadQueryInRender} = require('./loadQuery');
18
- const useFragmentNode = require('./useFragmentNode');
19
19
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
20
20
  const {useDebugValue} = require('react');
21
21
  const {getFragment} = require('relay-runtime');
@@ -31,17 +31,23 @@ declare function useFragment<TFragmentType: FragmentType, TData>(
31
31
  key: HasSpread<TFragmentType>,
32
32
  ): TData;
33
33
 
34
+ // if the key is nullable, return nullable value
35
+ declare function useFragment<TFragmentType: FragmentType, TData>(
36
+ fragment: Fragment<TFragmentType, TData>,
37
+ key: ?HasSpread<TFragmentType>,
38
+ ): ?TData;
39
+
34
40
  // if the key is a non-nullable array of keys, return non-nullable array
35
41
  declare function useFragment<TFragmentType: FragmentType, TData>(
36
42
  fragment: Fragment<TFragmentType, TData>,
37
43
  key: $ReadOnlyArray<HasSpread<TFragmentType>>,
38
44
  ): TData;
39
45
 
40
- // if the key is null/void, return null/void value
46
+ // if the key is a nullable array of keys, return nullable array
41
47
  declare function useFragment<TFragmentType: FragmentType, TData>(
42
48
  fragment: Fragment<TFragmentType, TData>,
43
- key: null | void,
44
- ): null | void;
49
+ key: ?$ReadOnlyArray<HasSpread<TFragmentType>>,
50
+ ): ?TData;
45
51
 
46
52
  function useFragment_LEGACY(fragment: GraphQLTaggedNode, key: mixed): mixed {
47
53
  // We need to use this hook in order to be able to track if
@@ -22,13 +22,13 @@ import type {
22
22
  import type {ReaderFragment} from 'relay-runtime/util/ReaderNode';
23
23
 
24
24
  const HooksImplementation = require('./HooksImplementation');
25
+ const useFragmentNode = require('./legacy/useFragmentNode');
25
26
  const ProfilerContext = require('./ProfilerContext');
26
27
  const {
27
28
  getQueryCacheIdentifier,
28
29
  getQueryResourceForEnvironment,
29
30
  } = require('./QueryResource');
30
31
  const useFetchTrackingRef = require('./useFetchTrackingRef');
31
- const useFragmentNode = require('./useFragmentNode');
32
32
  const useRelayEnvironment = require('./useRelayEnvironment');
33
33
  const React = require('react');
34
34
 
@@ -11,9 +11,9 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ import type {Options} from './legacy/useRefetchableFragmentNode';
14
15
  import type {LoadMoreFn, UseLoadMoreFunctionArgs} from './useLoadMoreFunction';
15
16
  import type {RefetchFn} from './useRefetchableFragment';
16
- import type {Options} from './useRefetchableFragmentNode';
17
17
  import type {
18
18
  FragmentType,
19
19
  GraphQLResponse,
@@ -23,8 +23,8 @@ import type {
23
23
  } from 'relay-runtime';
24
24
 
25
25
  const HooksImplementation = require('./HooksImplementation');
26
+ const useRefetchableFragmentNode = require('./legacy/useRefetchableFragmentNode');
26
27
  const useLoadMoreFunction = require('./useLoadMoreFunction');
27
- const useRefetchableFragmentNode = require('./useRefetchableFragmentNode');
28
28
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
29
29
  const {useCallback, useDebugValue, useState} = require('react');
30
30
  const {
@@ -11,7 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {Options} from './useRefetchableFragmentNode';
14
+ import type {Options} from './legacy/useRefetchableFragmentNode';
15
15
  import type {
16
16
  Disposable,
17
17
  FragmentType,
@@ -20,7 +20,7 @@ import type {
20
20
  } from 'relay-runtime';
21
21
 
22
22
  const HooksImplementation = require('./HooksImplementation');
23
- const useRefetchableFragmentNode = require('./useRefetchableFragmentNode');
23
+ const useRefetchableFragmentNode = require('./legacy/useRefetchableFragmentNode');
24
24
  const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
25
25
  const {useDebugValue} = require('react');
26
26
  const {getFragment} = require('relay-runtime');