react-relay 18.0.0 → 18.2.0

Sign up to get free protection for your applications and to get access to all the features.
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;