react-relay 18.0.0 → 18.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayFragmentContainer.js.flow +2 -5
  3. package/buildReactRelayContainer.js.flow +1 -0
  4. package/hooks.js +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +3 -0
  7. package/legacy.js +1 -1
  8. package/lib/index.js +2 -0
  9. package/lib/relay-hooks/getConnectionState.js +47 -0
  10. package/lib/relay-hooks/legacy/FragmentResource.js +3 -8
  11. package/lib/relay-hooks/loadQuery.js +5 -14
  12. package/lib/relay-hooks/readFragmentInternal.js +2 -4
  13. package/lib/relay-hooks/useFragmentInternal.js +1 -1
  14. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +3 -10
  15. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +6 -28
  16. package/lib/relay-hooks/useLoadMoreFunction.js +10 -43
  17. package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +130 -0
  18. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +227 -0
  19. package/lib/relay-hooks/useQueryLoader.js +8 -0
  20. package/lib/relay-hooks/useQueryLoader_EXPERIMENTAL.js +120 -0
  21. package/package.json +2 -2
  22. package/react-relay-hooks.js +2 -2
  23. package/react-relay-hooks.min.js +2 -2
  24. package/react-relay-legacy.js +1 -1
  25. package/react-relay-legacy.min.js +1 -1
  26. package/react-relay.js +2 -2
  27. package/react-relay.min.js +2 -2
  28. package/relay-hooks/EntryPointTypes.flow.js.flow +2 -2
  29. package/relay-hooks/MatchContainer.js.flow +1 -1
  30. package/relay-hooks/getConnectionState.js.flow +97 -0
  31. package/relay-hooks/legacy/FragmentResource.js.flow +4 -16
  32. package/relay-hooks/loadQuery.js.flow +30 -38
  33. package/relay-hooks/readFragmentInternal.js.flow +1 -10
  34. package/relay-hooks/useFragmentInternal.js.flow +1 -1
  35. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +7 -22
  36. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +8 -56
  37. package/relay-hooks/useLoadMoreFunction.js.flow +14 -80
  38. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +280 -0
  39. package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +433 -0
  40. package/relay-hooks/useQueryLoader.js.flow +27 -3
  41. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +253 -0
@@ -34,7 +34,6 @@ const {
34
34
  __internal: {fetchQueryDeduped},
35
35
  Observable,
36
36
  PreloadableQueryRegistry,
37
- RelayFeatureFlags,
38
37
  ReplaySubject,
39
38
  createOperationDescriptor,
40
39
  getRequest,
@@ -140,34 +139,29 @@ function loadQuery<
140
139
  // `source` observable is returned.
141
140
  didMakeNetworkRequest = true;
142
141
 
143
- let observable;
144
142
  const subject = new ReplaySubject<GraphQLResponse>();
145
- if (RelayFeatureFlags.ENABLE_LOAD_QUERY_REQUEST_DEDUPING === true) {
146
- // Here, we are calling fetchQueryDeduped at the network layer level,
147
- // which ensures that only a single network request is active for a given
148
- // (environment, identifier) pair.
149
- // Since network requests can be started /before/ we have the query ast
150
- // necessary to process the results, we need to dedupe the raw requests
151
- // separately from deduping the operation execution; specifically,
152
- // if `loadQuery` is called multiple times before the query ast is available,
153
- // we still want the network request to be deduped.
154
- // - If a duplicate active network request is found, it will return an
155
- // Observable that replays the events of the already active request.
156
- // - If no duplicate active network request is found, it will call the fetchFn
157
- // to start the request, and return an Observable that will replay
158
- // the events from the network request.
159
- // We provide an extra key to the identifier to distinguish deduping
160
- // of raw network requests vs deduping of operation executions.
161
- const identifier: RequestIdentifier =
162
- 'raw-network-request-' + getRequestIdentifier(params, variables);
163
- observable = fetchQueryDeduped(environment, identifier, () => {
164
- const network = environment.getNetwork();
165
- return network.execute(params, variables, networkCacheConfig);
166
- });
167
- } else {
143
+
144
+ // Here, we are calling fetchQueryDeduped at the network layer level,
145
+ // which ensures that only a single network request is active for a given
146
+ // (environment, identifier) pair.
147
+ // Since network requests can be started /before/ we have the query ast
148
+ // necessary to process the results, we need to dedupe the raw requests
149
+ // separately from deduping the operation execution; specifically,
150
+ // if `loadQuery` is called multiple times before the query ast is available,
151
+ // we still want the network request to be deduped.
152
+ // - If a duplicate active network request is found, it will return an
153
+ // Observable that replays the events of the already active request.
154
+ // - If no duplicate active network request is found, it will call the fetchFn
155
+ // to start the request, and return an Observable that will replay
156
+ // the events from the network request.
157
+ // We provide an extra key to the identifier to distinguish deduping
158
+ // of raw network requests vs deduping of operation executions.
159
+ const identifier: RequestIdentifier =
160
+ 'raw-network-request-' + getRequestIdentifier(params, variables);
161
+ const observable = fetchQueryDeduped(environment, identifier, () => {
168
162
  const network = environment.getNetwork();
169
- observable = network.execute(params, variables, networkCacheConfig);
170
- }
163
+ return network.execute(params, variables, networkCacheConfig);
164
+ });
171
165
 
172
166
  const {unsubscribe} = observable.subscribe({
173
167
  error(err) {
@@ -196,17 +190,15 @@ function loadQuery<
196
190
  operation: OperationDescriptor,
197
191
  fetchFn: () => Observable<GraphQLResponse>,
198
192
  ) => {
199
- if (RelayFeatureFlags.ENABLE_LOAD_QUERY_REQUEST_DEDUPING === true) {
200
- // N.B. at this point, if we're calling execute with a query ast (OperationDescriptor),
201
- // we are guaranteed to have started a network request. We set this to
202
- // true here as well since `makeNetworkRequest` might get skipped in the case
203
- // where the query ast is already available and the query executions get deduped.
204
- // Even if the execution gets deduped below, we still wan't to return
205
- // an observable that provides the replayed network events for the query,
206
- // so we set this to true before deduping, to guarantee that the `source`
207
- // observable is returned.
208
- didMakeNetworkRequest = true;
209
- }
193
+ // N.B. at this point, if we're calling execute with a query ast (OperationDescriptor),
194
+ // we are guaranteed to have started a network request. We set this to
195
+ // true here as well since `makeNetworkRequest` might get skipped in the case
196
+ // where the query ast is already available and the query executions get deduped.
197
+ // Even if the execution gets deduped below, we still wan't to return
198
+ // an observable that provides the replayed network events for the query,
199
+ // so we set this to true before deduping, to guarantee that the `source`
200
+ // observable is returned.
201
+ didMakeNetworkRequest = true;
210
202
 
211
203
  // Here, we are calling fetchQueryDeduped, which ensures that only
212
204
  // a single operation is active for a given (environment, identifier) pair,
@@ -85,20 +85,11 @@ function handlePotentialSnapshotErrorsForState(
85
85
  if (state.kind === 'singular') {
86
86
  handlePotentialSnapshotErrors(
87
87
  environment,
88
- state.snapshot.missingRequiredFields,
89
- state.snapshot.relayResolverErrors,
90
88
  state.snapshot.errorResponseFields,
91
- state.snapshot.selector.node.metadata?.throwOnFieldError ?? false,
92
89
  );
93
90
  } else if (state.kind === 'plural') {
94
91
  for (const snapshot of state.snapshots) {
95
- handlePotentialSnapshotErrors(
96
- environment,
97
- snapshot.missingRequiredFields,
98
- snapshot.relayResolverErrors,
99
- snapshot.errorResponseFields,
100
- snapshot.selector.node.metadata?.throwOnFieldError ?? false,
101
- );
92
+ handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
102
93
  }
103
94
  }
104
95
  }
@@ -24,7 +24,7 @@ hook useFragmentInternal(
24
24
  hookDisplayName: string,
25
25
  queryOptions?: FragmentQueryOptions,
26
26
  ): ?SelectorData | Array<?SelectorData> {
27
- if (RelayFeatureFlags.ENABLE_USE_FRAGMENT_EXPERIMENTAL) {
27
+ if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
28
28
  // $FlowFixMe[react-rule-hook] - the condition is static
29
29
  return useFragmentInternal_EXPERIMENTAL(
30
30
  fragmentNode,
@@ -14,6 +14,7 @@
14
14
  import type {QueryResult} from './QueryResource';
15
15
  import type {
16
16
  CacheConfig,
17
+ DataID,
17
18
  FetchPolicy,
18
19
  IEnvironment,
19
20
  ReaderFragment,
@@ -21,10 +22,7 @@ import type {
21
22
  SelectorData,
22
23
  Snapshot,
23
24
  } from 'relay-runtime';
24
- import type {
25
- MissingClientEdgeRequestInfo,
26
- MissingLiveResolverField,
27
- } from 'relay-runtime/store/RelayStoreTypes';
25
+ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreTypes';
28
26
 
29
27
  const {getQueryResourceForEnvironment} = require('./QueryResource');
30
28
  const useRelayEnvironment = require('./useRelayEnvironment');
@@ -89,13 +87,13 @@ function getMissingClientEdges(
89
87
 
90
88
  function getSuspendingLiveResolver(
91
89
  state: FragmentState,
92
- ): $ReadOnlyArray<MissingLiveResolverField> | null {
90
+ ): $ReadOnlyArray<DataID> | null {
93
91
  if (state.kind === 'bailout') {
94
92
  return null;
95
93
  } else if (state.kind === 'singular') {
96
94
  return state.snapshot.missingLiveResolverFields ?? null;
97
95
  } else {
98
- let missingFields: null | Array<MissingLiveResolverField> = null;
96
+ let missingFields: null | Array<DataID> = null;
99
97
  for (const snapshot of state.snapshots) {
100
98
  if (snapshot.missingLiveResolverFields) {
101
99
  missingFields = missingFields ?? [];
@@ -115,20 +113,11 @@ function handlePotentialSnapshotErrorsForState(
115
113
  if (state.kind === 'singular') {
116
114
  handlePotentialSnapshotErrors(
117
115
  environment,
118
- state.snapshot.missingRequiredFields,
119
- state.snapshot.relayResolverErrors,
120
116
  state.snapshot.errorResponseFields,
121
- state.snapshot.selector.node.metadata?.throwOnFieldError ?? false,
122
117
  );
123
118
  } else if (state.kind === 'plural') {
124
119
  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
- );
120
+ handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
132
121
  }
133
122
  }
134
123
  }
@@ -164,8 +153,6 @@ function handleMissedUpdates(
164
153
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
165
154
  seenRecords: currentSnapshot.seenRecords,
166
155
  selector: currentSnapshot.selector,
167
- missingRequiredFields: currentSnapshot.missingRequiredFields,
168
- relayResolverErrors: currentSnapshot.relayResolverErrors,
169
156
  errorResponseFields: currentSnapshot.errorResponseFields,
170
157
  };
171
158
  return [
@@ -190,8 +177,6 @@ function handleMissedUpdates(
190
177
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
191
178
  seenRecords: currentSnapshot.seenRecords,
192
179
  selector: currentSnapshot.selector,
193
- missingRequiredFields: currentSnapshot.missingRequiredFields,
194
- relayResolverErrors: currentSnapshot.relayResolverErrors,
195
180
  errorResponseFields: currentSnapshot.errorResponseFields,
196
181
  };
197
182
  if (updatedData !== snapshot.data) {
@@ -521,8 +506,8 @@ hook useFragmentInternal(
521
506
  const suspendingLiveResolvers = getSuspendingLiveResolver(state);
522
507
  if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
523
508
  throw Promise.all(
524
- suspendingLiveResolvers.map(({liveStateID}) => {
525
- // $FlowFixMe[prop-missing] This is expected to be a LiveResolverStore
509
+ suspendingLiveResolvers.map(liveStateID => {
510
+ // $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
526
511
  return environment.getStore().getLiveResolverPromise(liveStateID);
527
512
  }),
528
513
  );
@@ -14,6 +14,7 @@
14
14
  import type {QueryResult} from './QueryResource';
15
15
  import type {
16
16
  CacheConfig,
17
+ DataID,
17
18
  FetchPolicy,
18
19
  IEnvironment,
19
20
  ReaderFragment,
@@ -21,10 +22,7 @@ import type {
21
22
  SelectorData,
22
23
  Snapshot,
23
24
  } from 'relay-runtime';
24
- import type {
25
- MissingClientEdgeRequestInfo,
26
- MissingLiveResolverField,
27
- } from 'relay-runtime/store/RelayStoreTypes';
25
+ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreTypes';
28
26
 
29
27
  const {getQueryResourceForEnvironment} = require('./QueryResource');
30
28
  const useRelayEnvironment = require('./useRelayEnvironment');
@@ -101,13 +99,13 @@ function getMissingClientEdges(
101
99
 
102
100
  function getSuspendingLiveResolver(
103
101
  state: FragmentState,
104
- ): $ReadOnlyArray<MissingLiveResolverField> | null {
102
+ ): $ReadOnlyArray<DataID> | null {
105
103
  if (state.kind === 'bailout') {
106
104
  return null;
107
105
  } else if (state.kind === 'singular') {
108
106
  return state.snapshot.missingLiveResolverFields ?? null;
109
107
  } else {
110
- let missingFields: null | Array<MissingLiveResolverField> = null;
108
+ let missingFields: null | Array<DataID> = null;
111
109
  for (const snapshot of state.snapshots) {
112
110
  if (snapshot.missingLiveResolverFields) {
113
111
  missingFields = missingFields ?? [];
@@ -127,20 +125,11 @@ function handlePotentialSnapshotErrorsForState(
127
125
  if (state.kind === 'singular') {
128
126
  handlePotentialSnapshotErrors(
129
127
  environment,
130
- state.snapshot.missingRequiredFields,
131
- state.snapshot.relayResolverErrors,
132
128
  state.snapshot.errorResponseFields,
133
- state.snapshot.selector.node.metadata?.throwOnFieldError ?? false,
134
129
  );
135
130
  } else if (state.kind === 'plural') {
136
131
  for (const snapshot of state.snapshots) {
137
- handlePotentialSnapshotErrors(
138
- environment,
139
- snapshot.missingRequiredFields,
140
- snapshot.relayResolverErrors,
141
- snapshot.errorResponseFields,
142
- snapshot.selector.node.metadata?.throwOnFieldError ?? false,
143
- );
132
+ handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
144
133
  }
145
134
  }
146
135
  }
@@ -176,8 +165,6 @@ function handleMissedUpdates(
176
165
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
177
166
  seenRecords: currentSnapshot.seenRecords,
178
167
  selector: currentSnapshot.selector,
179
- missingRequiredFields: currentSnapshot.missingRequiredFields,
180
- relayResolverErrors: currentSnapshot.relayResolverErrors,
181
168
  errorResponseFields: currentSnapshot.errorResponseFields,
182
169
  };
183
170
  return [
@@ -204,8 +191,6 @@ function handleMissedUpdates(
204
191
  missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
205
192
  seenRecords: currentSnapshot.seenRecords,
206
193
  selector: currentSnapshot.selector,
207
- missingRequiredFields: currentSnapshot.missingRequiredFields,
208
- relayResolverErrors: currentSnapshot.relayResolverErrors,
209
194
  errorResponseFields: currentSnapshot.errorResponseFields,
210
195
  };
211
196
  if (updatedData !== snapshot.data) {
@@ -271,7 +256,6 @@ function subscribeToSnapshot(
271
256
  environment: IEnvironment,
272
257
  state: FragmentState,
273
258
  setState: StateUpdaterFunction<FragmentState>,
274
- pendingStateRef: {current: number | null},
275
259
  ): () => void {
276
260
  if (state.kind === 'bailout') {
277
261
  return () => {};
@@ -307,8 +291,6 @@ function subscribeToSnapshot(
307
291
  environment: state.environment,
308
292
  };
309
293
  }
310
- pendingStateRef.current =
311
- nextState.kind === 'singular' ? nextState.epoch : null;
312
294
  return nextState;
313
295
  });
314
296
  });
@@ -353,8 +335,6 @@ function subscribeToSnapshot(
353
335
  environment: state.environment,
354
336
  };
355
337
  }
356
- pendingStateRef.current =
357
- nextState.kind === 'plural' ? nextState.epoch : null;
358
338
  return nextState;
359
339
  });
360
340
  }),
@@ -541,8 +521,8 @@ hook useFragmentInternal_EXPERIMENTAL(
541
521
  const suspendingLiveResolvers = getSuspendingLiveResolver(state);
542
522
  if (suspendingLiveResolvers != null && suspendingLiveResolvers.length > 0) {
543
523
  throw Promise.all(
544
- suspendingLiveResolvers.map(({liveStateID}) => {
545
- // $FlowFixMe[prop-missing] This is expected to be a LiveResolverStore
524
+ suspendingLiveResolvers.map(liveStateID => {
525
+ // $FlowFixMe[prop-missing] This is expected to be a RelayModernStore
546
526
  return environment.getStore().getLiveResolverPromise(liveStateID);
547
527
  }),
548
528
  );
@@ -578,11 +558,6 @@ hook useFragmentInternal_EXPERIMENTAL(
578
558
  // they're missing even though we are out of options for possibly fetching them:
579
559
  handlePotentialSnapshotErrorsForState(environment, state);
580
560
 
581
- // Ref that stores the epoch of the pending setState, if any. This is used to check
582
- // if the state we're rendering is at least as current as the pending update, and
583
- // force a refresh if stale.
584
- const pendingStateEpochRef = useRef<number | null>(null);
585
-
586
561
  // We emulate CRUD effects using a ref and two effects:
587
562
  // - The ref tracks the current state (including updates from the subscription)
588
563
  // and the dispose function for the current subscription. This is null until
@@ -647,7 +622,6 @@ hook useFragmentInternal_EXPERIMENTAL(
647
622
  state.environment,
648
623
  stateForSubscription,
649
624
  setState,
650
- pendingStateEpochRef,
651
625
  );
652
626
  storeSubscriptionRef.current = {
653
627
  dispose,
@@ -657,12 +631,7 @@ hook useFragmentInternal_EXPERIMENTAL(
657
631
  }, [state]);
658
632
  useEffect(() => {
659
633
  if (storeSubscriptionRef.current == null && state.kind !== 'bailout') {
660
- const dispose = subscribeToSnapshot(
661
- state.environment,
662
- state,
663
- setState,
664
- pendingStateEpochRef,
665
- );
634
+ const dispose = subscribeToSnapshot(state.environment, state, setState);
666
635
  storeSubscriptionRef.current = {
667
636
  dispose,
668
637
  selector: state.selector,
@@ -677,23 +646,6 @@ hook useFragmentInternal_EXPERIMENTAL(
677
646
  // simulating a CRUD effect
678
647
  }, []);
679
648
 
680
- // If a low-priority update was queued and hasn't rendered yet, render it now
681
- if (
682
- pendingStateEpochRef.current !== null &&
683
- pendingStateEpochRef.current !== state.epoch
684
- ) {
685
- const updates = handleMissedUpdates(environment, state);
686
- if (updates != null) {
687
- const [hasStateUpdates, updatedState] = updates;
688
- if (hasStateUpdates) {
689
- setState(updatedState);
690
- state = updatedState;
691
- }
692
- }
693
- }
694
- // $FlowFixMe[react-rule-unsafe-ref]
695
- pendingStateEpochRef.current = null;
696
-
697
649
  let data: ?SelectorData | Array<?SelectorData>;
698
650
  if (isPlural) {
699
651
  // Plural fragments require allocating an array of the snapshot data values,
@@ -22,20 +22,21 @@ import type {
22
22
  Variables,
23
23
  } from 'relay-runtime';
24
24
 
25
+ const getConnectionState = require('./getConnectionState');
25
26
  const useFetchTrackingRef = require('./useFetchTrackingRef');
26
27
  const useIsMountedRef = require('./useIsMountedRef');
27
28
  const useIsOperationNodeActive = require('./useIsOperationNodeActive');
29
+ const useLoadMoreFunction_EXPERIMENTAL = require('./useLoadMoreFunction_EXPERIMENTAL');
28
30
  const useRelayEnvironment = require('./useRelayEnvironment');
29
31
  const invariant = require('invariant');
30
32
  const {useCallback, useEffect, useState} = require('react');
31
33
  const {
32
34
  __internal: {fetchQuery},
33
- ConnectionInterface,
35
+ RelayFeatureFlags,
34
36
  createOperationDescriptor,
35
37
  getPaginationVariables,
36
38
  getRefetchMetadata,
37
39
  getSelector,
38
- getValueAtPath,
39
40
  } = require('relay-runtime');
40
41
  const warning = require('warning');
41
42
 
@@ -63,6 +64,17 @@ export type UseLoadMoreFunctionArgs = {
63
64
 
64
65
  hook useLoadMoreFunction<TVariables: Variables>(
65
66
  args: UseLoadMoreFunctionArgs,
67
+ ): [LoadMoreFn<TVariables>, boolean, () => void] {
68
+ if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
69
+ // $FlowFixMe[react-rule-hook] - the condition is static
70
+ return useLoadMoreFunction_EXPERIMENTAL(args);
71
+ }
72
+ // $FlowFixMe[react-rule-hook] - the condition is static
73
+ return useLoadMoreFunction_CURRENT(args);
74
+ }
75
+
76
+ hook useLoadMoreFunction_CURRENT<TVariables: Variables>(
77
+ args: UseLoadMoreFunctionArgs,
66
78
  ): [LoadMoreFn<TVariables>, boolean, () => void] {
67
79
  const {
68
80
  direction,
@@ -269,82 +281,4 @@ hook useLoadMoreFunction<TVariables: Variables>(
269
281
  return [loadMore, hasMore, disposeFetch];
270
282
  }
271
283
 
272
- function getConnectionState(
273
- direction: Direction,
274
- fragmentNode: ReaderFragment,
275
- fragmentData: mixed,
276
- connectionPathInFragmentData: $ReadOnlyArray<string | number>,
277
- ): {
278
- cursor: ?string,
279
- hasMore: boolean,
280
- } {
281
- const {
282
- EDGES,
283
- PAGE_INFO,
284
- HAS_NEXT_PAGE,
285
- HAS_PREV_PAGE,
286
- END_CURSOR,
287
- START_CURSOR,
288
- } = ConnectionInterface.get();
289
- const connection = getValueAtPath(fragmentData, connectionPathInFragmentData);
290
- if (connection == null) {
291
- return {cursor: null, hasMore: false};
292
- }
293
-
294
- invariant(
295
- typeof connection === 'object',
296
- 'Relay: Expected connection in fragment `%s` to have been `null`, or ' +
297
- 'a plain object with %s and %s properties. Instead got `%s`.',
298
- fragmentNode.name,
299
- EDGES,
300
- PAGE_INFO,
301
- connection,
302
- );
303
-
304
- const edges = connection[EDGES];
305
- const pageInfo = connection[PAGE_INFO];
306
- if (edges == null || pageInfo == null) {
307
- return {cursor: null, hasMore: false};
308
- }
309
-
310
- invariant(
311
- Array.isArray(edges),
312
- 'Relay: Expected connection in fragment `%s` to have a plural `%s` field. ' +
313
- 'Instead got `%s`.',
314
- fragmentNode.name,
315
- EDGES,
316
- edges,
317
- );
318
- invariant(
319
- typeof pageInfo === 'object',
320
- 'Relay: Expected connection in fragment `%s` to have a `%s` field. ' +
321
- 'Instead got `%s`.',
322
- fragmentNode.name,
323
- PAGE_INFO,
324
- pageInfo,
325
- );
326
-
327
- const cursor =
328
- direction === 'forward'
329
- ? pageInfo[END_CURSOR] ?? null
330
- : pageInfo[START_CURSOR] ?? null;
331
- invariant(
332
- cursor === null || typeof cursor === 'string',
333
- 'Relay: Expected page info for connection in fragment `%s` to have a ' +
334
- 'valid `%s`. Instead got `%s`.',
335
- fragmentNode.name,
336
- START_CURSOR,
337
- cursor,
338
- );
339
-
340
- let hasMore;
341
- if (direction === 'forward') {
342
- hasMore = cursor != null && pageInfo[HAS_NEXT_PAGE] === true;
343
- } else {
344
- hasMore = cursor != null && pageInfo[HAS_PREV_PAGE] === true;
345
- }
346
-
347
- return {cursor, hasMore};
348
- }
349
-
350
284
  module.exports = useLoadMoreFunction;