UNPKG

9.8 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// flowlint ambiguous-object-type:error
12'use strict'; // flowlint-next-line untyped-import:off
13
14var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
15
16var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
17
18var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
19
20function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
21
22function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
23
24var Scheduler = require('scheduler');
25
26var getPaginationMetadata = require('./getPaginationMetadata');
27
28var invariant = require("fbjs/lib/invariant");
29
30var useLoadMoreFunction = require('./useLoadMoreFunction');
31
32var useRefetchableFragmentNode = require('./useRefetchableFragmentNode');
33
34var useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning');
35
36var warning = require("fbjs/lib/warning");
37
38var _require = require('react'),
39 useCallback = _require.useCallback,
40 useEffect = _require.useEffect,
41 useRef = _require.useRef,
42 useState = _require.useState;
43
44var _require2 = require('relay-runtime'),
45 getFragment = _require2.getFragment,
46 getFragmentIdentifier = _require2.getFragmentIdentifier;
47
48function useBlockingPaginationFragment(fragmentInput, parentFragmentRef) {
49 var componentDisplayName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'useBlockingPaginationFragment()';
50 var fragmentNode = getFragment(fragmentInput);
51 useStaticFragmentNodeWarning(fragmentNode, "first argument of ".concat(componentDisplayName));
52
53 var _getPaginationMetadat = getPaginationMetadata(fragmentNode, componentDisplayName),
54 connectionPathInFragmentData = _getPaginationMetadat.connectionPathInFragmentData,
55 identifierField = _getPaginationMetadat.identifierField,
56 paginationRequest = _getPaginationMetadat.paginationRequest,
57 paginationMetadata = _getPaginationMetadat.paginationMetadata,
58 stream = _getPaginationMetadat.stream;
59
60 !(stream === false) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: @stream_connection is not compatible with `useBlockingPaginationFragment`. ' + 'Use `useStreamingPaginationFragment` instead.') : invariant(false) : void 0;
61
62 var _useRefetchableFragme = useRefetchableFragmentNode(fragmentNode, parentFragmentRef, componentDisplayName),
63 fragmentData = _useRefetchableFragme.fragmentData,
64 fragmentRef = _useRefetchableFragme.fragmentRef,
65 refetch = _useRefetchableFragme.refetch,
66 disableStoreUpdates = _useRefetchableFragme.disableStoreUpdates,
67 enableStoreUpdates = _useRefetchableFragme.enableStoreUpdates;
68
69 var fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef); // Backward pagination
70
71 var _useLoadMore = useLoadMore({
72 componentDisplayName: componentDisplayName,
73 connectionPathInFragmentData: connectionPathInFragmentData,
74 direction: 'backward',
75 disableStoreUpdates: disableStoreUpdates,
76 enableStoreUpdates: enableStoreUpdates,
77 fragmentData: fragmentData,
78 fragmentIdentifier: fragmentIdentifier,
79 fragmentNode: fragmentNode,
80 fragmentRef: fragmentRef,
81 identifierField: identifierField,
82 paginationMetadata: paginationMetadata,
83 paginationRequest: paginationRequest
84 }),
85 loadPrevious = _useLoadMore[0],
86 hasPrevious = _useLoadMore[1],
87 disposeFetchPrevious = _useLoadMore[2]; // Forward pagination
88
89
90 var _useLoadMore2 = useLoadMore({
91 componentDisplayName: componentDisplayName,
92 connectionPathInFragmentData: connectionPathInFragmentData,
93 direction: 'forward',
94 disableStoreUpdates: disableStoreUpdates,
95 enableStoreUpdates: enableStoreUpdates,
96 fragmentData: fragmentData,
97 fragmentIdentifier: fragmentIdentifier,
98 fragmentNode: fragmentNode,
99 fragmentRef: fragmentRef,
100 identifierField: identifierField,
101 paginationMetadata: paginationMetadata,
102 paginationRequest: paginationRequest
103 }),
104 loadNext = _useLoadMore2[0],
105 hasNext = _useLoadMore2[1],
106 disposeFetchNext = _useLoadMore2[2];
107
108 var refetchPagination = useCallback(function (variables, options) {
109 disposeFetchNext();
110 disposeFetchPrevious();
111 return refetch(variables, _objectSpread({}, options, {
112 __environment: undefined
113 }));
114 }, [disposeFetchNext, disposeFetchPrevious, refetch]);
115 return {
116 data: fragmentData,
117 loadNext: loadNext,
118 loadPrevious: loadPrevious,
119 hasNext: hasNext,
120 hasPrevious: hasPrevious,
121 refetch: refetchPagination
122 };
123}
124
125function useLoadMore(args) {
126 var disableStoreUpdates = args.disableStoreUpdates,
127 enableStoreUpdates = args.enableStoreUpdates,
128 loadMoreArgs = (0, _objectWithoutPropertiesLoose2["default"])(args, ["disableStoreUpdates", "enableStoreUpdates"]);
129
130 var _useState = useState(null),
131 requestPromise = _useState[0],
132 setRequestPromise = _useState[1];
133
134 var requestPromiseRef = useRef(null);
135 var promiseResolveRef = useRef(null);
136
137 var promiseResolve = function promiseResolve() {
138 if (promiseResolveRef.current != null) {
139 promiseResolveRef.current();
140 promiseResolveRef.current = null;
141 }
142 };
143
144 var handleReset = function handleReset() {
145 promiseResolve();
146 };
147
148 var observer = {
149 complete: promiseResolve,
150 // NOTE: loadMore is a no-op if a request is already in flight, so we
151 // can safely assume that `start` will only be called once while a
152 // request is in flight.
153 start: function start() {
154 // NOTE: We disable store updates when we suspend to ensure
155 // that higher-pri updates from the Relay store don't disrupt
156 // any Suspense timeouts passed via withSuspenseConfig.
157 disableStoreUpdates();
158 var promise = new Promise(function (resolve) {
159 promiseResolveRef.current = function () {
160 requestPromiseRef.current = null;
161 resolve();
162 };
163 });
164 requestPromiseRef.current = promise;
165 setRequestPromise(promise);
166 },
167 // NOTE: Since streaming is disallowed with this hook, this means that the
168 // first payload will always contain the entire next page of items,
169 // while subsequent paylaods will contain @defer'd payloads.
170 // This allows us to unsuspend here, on the first payload, and allow
171 // descendant components to suspend on their respective @defer payloads
172 next: promiseResolve,
173 // TODO: Handle error; we probably don't want to throw an error
174 // and blow away the whole list of items.
175 error: promiseResolve
176 };
177
178 var _useLoadMoreFunction = useLoadMoreFunction(_objectSpread({}, loadMoreArgs, {
179 observer: observer,
180 onReset: handleReset
181 })),
182 _loadMore = _useLoadMoreFunction[0],
183 hasMore = _useLoadMoreFunction[1],
184 disposeFetch = _useLoadMoreFunction[2]; // NOTE: To determine if we need to suspend, we check that the promise in
185 // state is the same as the promise on the ref, which ensures that we
186 // wont incorrectly suspend on other higher-pri updates before the update
187 // to suspend has committed.
188
189
190 if (requestPromise != null && requestPromise === requestPromiseRef.current) {
191 throw requestPromise;
192 }
193
194 useEffect(function () {
195 if (requestPromise !== requestPromiseRef.current) {
196 // NOTE: After suspense pagination has resolved, we re-enable store updates
197 // for this fragment. This may cause the component to re-render if
198 // we missed any updates to the fragment data other than the pagination update.
199 enableStoreUpdates();
200 } // NOTE: We know the identity of enableStoreUpdates wont change
201 // eslint-disable-next-line react-hooks/exhaustive-deps
202
203 }, [requestPromise]);
204 var loadMore = useCallback(function () {
205 if (Scheduler.unstable_getCurrentPriorityLevel() < Scheduler.unstable_NormalPriority) {
206 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `%s` at a priority higher than ' + 'expected on fragment `%s` in `%s`. It looks like you tried to ' + 'call `refetch` under a high priority update, but updates that ' + 'can cause the component to suspend should be scheduled at ' + 'normal priority. Make sure you are calling `refetch` inside ' + '`startTransition()` from the `useSuspenseTransition()` hook.', args.direction === 'forward' ? 'loadNext' : 'loadPrevious', args.fragmentNode.name, args.componentDisplayName) : void 0;
207 }
208
209 for (var _len = arguments.length, callArgs = new Array(_len), _key = 0; _key < _len; _key++) {
210 callArgs[_key] = arguments[_key];
211 }
212
213 return _loadMore.apply(void 0, callArgs);
214 }, [_loadMore, args.componentDisplayName, args.direction, args.fragmentNode.name]);
215 return [loadMore, hasMore, disposeFetch];
216}
217
218module.exports = useBlockingPaginationFragment;
\No newline at end of file