UNPKG

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