UNPKG

10.5 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails oncall+relay
8 *
9 * @format
10 */
11'use strict'; // flowlint untyped-import:off
12
13var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
14
15var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
16
17var Scheduler = require('scheduler'); // flowlint untyped-import:error
18
19
20var getPaginationVariables = require('./getPaginationVariables');
21
22var getValueAtPath = require('./getValueAtPath');
23
24var invariant = require("fbjs/lib/invariant");
25
26var useFetchTrackingRef = require('./useFetchTrackingRef');
27
28var useIsMountedRef = require('./useIsMountedRef');
29
30var useRelayEnvironment = require('./useRelayEnvironment');
31
32var warning = require("fbjs/lib/warning");
33
34var _require = require('react'),
35 useCallback = _require.useCallback,
36 useEffect = _require.useEffect,
37 useState = _require.useState;
38
39var _require2 = require('relay-runtime'),
40 ConnectionInterface = _require2.ConnectionInterface,
41 _require2$__internal = _require2.__internal,
42 fetchQuery = _require2$__internal.fetchQuery,
43 hasRequestInFlight = _require2$__internal.hasRequestInFlight,
44 createOperationDescriptor = _require2.createOperationDescriptor,
45 getSelector = _require2.getSelector;
46
47function useLoadMoreFunction(args) {
48 var direction = args.direction,
49 fragmentNode = args.fragmentNode,
50 fragmentRef = args.fragmentRef,
51 fragmentIdentifier = args.fragmentIdentifier,
52 fragmentData = args.fragmentData,
53 connectionPathInFragmentData = args.connectionPathInFragmentData,
54 fragmentRefPathInResponse = args.fragmentRefPathInResponse,
55 paginationRequest = args.paginationRequest,
56 paginationMetadata = args.paginationMetadata,
57 componentDisplayName = args.componentDisplayName,
58 observer = args.observer,
59 onReset = args.onReset;
60 var environment = useRelayEnvironment();
61
62 var _useFetchTrackingRef = useFetchTrackingRef(),
63 isFetchingRef = _useFetchTrackingRef.isFetchingRef,
64 startFetch = _useFetchTrackingRef.startFetch,
65 disposeFetch = _useFetchTrackingRef.disposeFetch,
66 completeFetch = _useFetchTrackingRef.completeFetch; // $FlowFixMe
67
68
69 var dataID = fragmentData === null || fragmentData === void 0 ? void 0 : fragmentData.id;
70 var isMountedRef = useIsMountedRef();
71
72 var _useState = useState(environment),
73 mirroredEnvironment = _useState[0],
74 setMirroredEnvironment = _useState[1];
75
76 var _useState2 = useState(fragmentIdentifier),
77 mirroredFragmentIdentifier = _useState2[0],
78 setMirroredFragmentIdentifier = _useState2[1];
79
80 var shouldReset = environment !== mirroredEnvironment || fragmentIdentifier !== mirroredFragmentIdentifier;
81
82 if (shouldReset) {
83 disposeFetch();
84 onReset();
85 setMirroredEnvironment(environment);
86 setMirroredFragmentIdentifier(fragmentIdentifier);
87 }
88
89 var _getConnectionState = getConnectionState(direction, fragmentNode, fragmentData, connectionPathInFragmentData),
90 cursor = _getConnectionState.cursor,
91 hasMore = _getConnectionState.hasMore; // Dispose of pagination requests in flight when unmounting
92
93
94 useEffect(function () {
95 return function () {
96 disposeFetch();
97 };
98 }, [disposeFetch]);
99 var loadMore = useCallback(function (count, options) {
100 // TODO(T41131846): Fetch/Caching policies for loadMore
101 // TODO(T41140071): Handle loadMore while refetch is in flight and vice-versa
102 var onComplete = options === null || options === void 0 ? void 0 : options.onComplete;
103
104 if (isMountedRef.current !== true) {
105 // Bail out and warn if we're trying to paginate after the component
106 // has unmounted
107 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected fetch on unmounted component for fragment ' + '`%s` in `%s`. It looks like some instances of your component are ' + 'still trying to fetch data but they already unmounted. ' + 'Please make sure you clear all timers, intervals, ' + 'async calls, etc that may trigger a fetch.', fragmentNode.name, componentDisplayName) : void 0;
108 return {
109 dispose: function dispose() {}
110 };
111 }
112
113 var fragmentSelector = getSelector(fragmentNode, fragmentRef);
114 var isParentQueryInFlight = fragmentSelector != null && fragmentSelector.kind !== 'PluralReaderSelector' && hasRequestInFlight(environment, fragmentSelector.owner);
115
116 if (isFetchingRef.current === true || fragmentData == null || isParentQueryInFlight) {
117 if (fragmentSelector == null) {
118 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected fetch while using a null fragment ref ' + 'for fragment `%s` in `%s`. When fetching more items, we expect ' + "initial fragment data to be non-null. Please make sure you're " + 'passing a valid fragment ref to `%s` before paginating.', fragmentNode.name, componentDisplayName, componentDisplayName) : void 0;
119 }
120
121 if (onComplete) {
122 // We make sure to always call onComplete asynchronously to prevent
123 // accidental loops in product code.
124 Scheduler.unstable_next(function () {
125 return onComplete(null);
126 });
127 }
128
129 return {
130 dispose: function dispose() {}
131 };
132 }
133
134 !(fragmentSelector != null && fragmentSelector.kind !== 'PluralReaderSelector') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected to be able to find a non-plural fragment owner for ' + "fragment `%s` when using `%s`. If you're seeing this, " + 'this is likely a bug in Relay.', fragmentNode.name, componentDisplayName) : invariant(false) : void 0;
135 var parentVariables = fragmentSelector.owner.variables;
136 var fragmentVariables = fragmentSelector.variables;
137 var baseVariables = (0, _objectSpread2["default"])({}, parentVariables, fragmentVariables);
138 var paginationVariables = getPaginationVariables(direction, count, cursor, baseVariables, paginationMetadata); // TODO (T40777961): Tweak output of @refetchable transform to more
139 // easily tell if we need an $id in the refetch vars
140
141 if (fragmentRefPathInResponse.includes('node')) {
142 // @refetchable fragments are guaranteed to have an `id` selection
143 // if the type is Node or implements Node. Double-check that there
144 // actually is a value at runtime.
145 if (typeof dataID !== 'string') {
146 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected result to have a string ' + '`id` in order to refetch/paginate, got `%s`.', dataID) : void 0;
147 }
148
149 paginationVariables.id = dataID;
150 }
151
152 var paginationQuery = createOperationDescriptor(paginationRequest, paginationVariables);
153 fetchQuery(environment, paginationQuery, {
154 networkCacheConfig: {
155 force: true
156 }
157 }).subscribe((0, _objectSpread2["default"])({}, observer, {
158 start: function start(subscription) {
159 startFetch(subscription);
160 observer.start && observer.start(subscription);
161 },
162 complete: function complete() {
163 completeFetch();
164 observer.complete && observer.complete();
165 onComplete && onComplete(null);
166 },
167 error: function error(_error) {
168 completeFetch();
169 observer.error && observer.error(_error);
170 onComplete && onComplete(_error);
171 }
172 }));
173 return {
174 dispose: disposeFetch
175 };
176 }, // NOTE: We disable react-hooks-deps warning because all values
177 // inside paginationMetadata are static
178 // eslint-disable-next-line react-hooks/exhaustive-deps
179 [environment, dataID, direction, cursor, startFetch, disposeFetch, completeFetch, isFetchingRef, fragmentData, fragmentNode.name, fragmentRef, componentDisplayName]);
180 return [loadMore, hasMore, disposeFetch];
181}
182
183function getConnectionState(direction, fragmentNode, fragmentData, connectionPathInFragmentData) {
184 var _pageInfo$END_CURSOR, _pageInfo$START_CURSO;
185
186 var _ConnectionInterface$ = ConnectionInterface.get(),
187 EDGES = _ConnectionInterface$.EDGES,
188 PAGE_INFO = _ConnectionInterface$.PAGE_INFO,
189 HAS_NEXT_PAGE = _ConnectionInterface$.HAS_NEXT_PAGE,
190 HAS_PREV_PAGE = _ConnectionInterface$.HAS_PREV_PAGE,
191 END_CURSOR = _ConnectionInterface$.END_CURSOR,
192 START_CURSOR = _ConnectionInterface$.START_CURSOR;
193
194 var connection = getValueAtPath(fragmentData, connectionPathInFragmentData);
195
196 if (connection == null) {
197 return {
198 cursor: null,
199 hasMore: false
200 };
201 }
202
203 !(typeof connection === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected connection in fragment `%s` to have been `null`, or ' + 'a plain object with %s and %s properties. Instead got `%s`.', fragmentNode.name, EDGES, PAGE_INFO, connection) : invariant(false) : void 0;
204 var edges = connection[EDGES];
205 var pageInfo = connection[PAGE_INFO];
206
207 if (edges == null || pageInfo == null) {
208 return {
209 cursor: null,
210 hasMore: false
211 };
212 }
213
214 !Array.isArray(edges) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected connection in fragment `%s` to have a plural `%s` field. ' + 'Instead got `%s`.', fragmentNode.name, EDGES, edges) : invariant(false) : void 0;
215 !(typeof pageInfo === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected connection in fragment `%s` to have a `%s` field. ' + 'Instead got `%s`.', fragmentNode.name, PAGE_INFO, pageInfo) : invariant(false) : void 0;
216 var cursor = direction === 'forward' ? (_pageInfo$END_CURSOR = pageInfo[END_CURSOR]) !== null && _pageInfo$END_CURSOR !== void 0 ? _pageInfo$END_CURSOR : null : (_pageInfo$START_CURSO = pageInfo[START_CURSOR]) !== null && _pageInfo$START_CURSO !== void 0 ? _pageInfo$START_CURSO : null;
217 !(cursor === null || typeof cursor === 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected page info for connection in fragment `%s` to have a ' + 'valid `%s`. Instead got `%s`.', fragmentNode.name, START_CURSOR, cursor) : invariant(false) : void 0;
218 var hasMore;
219
220 if (direction === 'forward') {
221 hasMore = cursor != null && pageInfo[HAS_NEXT_PAGE] === true;
222 } else {
223 hasMore = cursor != null && pageInfo[HAS_PREV_PAGE] === true;
224 }
225
226 return {
227 cursor: cursor,
228 hasMore: hasMore
229 };
230}
231
232module.exports = useLoadMoreFunction;
\No newline at end of file