UNPKG

19.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// flowlint ambiguous-object-type:error
12'use strict';
13
14var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
15
16var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
17
18function 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; }
19
20function _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; }
21
22var ProfilerContext = require('./ProfilerContext'); // flowlint-next-line untyped-import:off
23
24
25var Scheduler = require('scheduler');
26
27var getRefetchMetadata = require('./getRefetchMetadata');
28
29var getValueAtPath = require('./getValueAtPath');
30
31var invariant = require("fbjs/lib/invariant");
32
33var useFetchTrackingRef = require('./useFetchTrackingRef');
34
35var useFragmentNode = require('./useFragmentNode');
36
37var useIsMountedRef = require('./useIsMountedRef');
38
39var useMemoVariables = require('./useMemoVariables');
40
41var useRelayEnvironment = require('./useRelayEnvironment');
42
43var warning = require("fbjs/lib/warning");
44
45var _require = require('./FragmentResource'),
46 getFragmentResourceForEnvironment = _require.getFragmentResourceForEnvironment;
47
48var _require2 = require('./QueryResource'),
49 getQueryResourceForEnvironment = _require2.getQueryResourceForEnvironment;
50
51var _require3 = require('react'),
52 useCallback = _require3.useCallback,
53 useContext = _require3.useContext,
54 useEffect = _require3.useEffect,
55 useMemo = _require3.useMemo,
56 useReducer = _require3.useReducer;
57
58var _require4 = require('relay-runtime'),
59 fetchQuery = _require4.__internal.fetchQuery,
60 createOperationDescriptor = _require4.createOperationDescriptor,
61 getFragmentIdentifier = _require4.getFragmentIdentifier,
62 getSelector = _require4.getSelector;
63
64function reducer(state, action) {
65 switch (action.type) {
66 case 'refetch':
67 {
68 var _action$environment;
69
70 return _objectSpread({}, state, {
71 refetchVariables: action.refetchVariables,
72 refetchGeneration: state.refetchGeneration + 1,
73 fetchPolicy: action.fetchPolicy,
74 renderPolicy: action.renderPolicy,
75 onComplete: action.onComplete,
76 refetchEnvironment: action.environment,
77 mirroredEnvironment: (_action$environment = action.environment) !== null && _action$environment !== void 0 ? _action$environment : state.mirroredEnvironment
78 });
79 }
80
81 case 'reset':
82 {
83 return {
84 fetchPolicy: undefined,
85 renderPolicy: undefined,
86 onComplete: undefined,
87 refetchVariables: null,
88 refetchGeneration: 0,
89 mirroredEnvironment: action.environment,
90 mirroredFragmentIdentifier: action.fragmentIdentifier
91 };
92 }
93
94 default:
95 {
96 action.type;
97 throw new Error('useRefetchableFragmentNode: Unexpected action type');
98 }
99 }
100}
101
102function useRefetchableFragmentNode(fragmentNode, parentFragmentRef, componentDisplayName) {
103 var parentEnvironment = useRelayEnvironment();
104
105 var _getRefetchMetadata = getRefetchMetadata(fragmentNode, componentDisplayName),
106 refetchableRequest = _getRefetchMetadata.refetchableRequest,
107 fragmentRefPathInResponse = _getRefetchMetadata.fragmentRefPathInResponse,
108 identifierField = _getRefetchMetadata.identifierField;
109
110 var fragmentIdentifier = getFragmentIdentifier(fragmentNode, parentFragmentRef);
111
112 var _useReducer = useReducer(reducer, {
113 fetchPolicy: undefined,
114 renderPolicy: undefined,
115 onComplete: undefined,
116 refetchVariables: null,
117 refetchGeneration: 0,
118 refetchEnvironment: null,
119 mirroredEnvironment: parentEnvironment,
120 mirroredFragmentIdentifier: fragmentIdentifier
121 }),
122 refetchState = _useReducer[0],
123 dispatch = _useReducer[1];
124
125 var _useFetchTrackingRef = useFetchTrackingRef(),
126 startFetch = _useFetchTrackingRef.startFetch,
127 disposeFetch = _useFetchTrackingRef.disposeFetch,
128 completeFetch = _useFetchTrackingRef.completeFetch;
129
130 var refetchVariables = refetchState.refetchVariables,
131 refetchGeneration = refetchState.refetchGeneration,
132 refetchEnvironment = refetchState.refetchEnvironment,
133 fetchPolicy = refetchState.fetchPolicy,
134 renderPolicy = refetchState.renderPolicy,
135 onComplete = refetchState.onComplete,
136 mirroredEnvironment = refetchState.mirroredEnvironment,
137 mirroredFragmentIdentifier = refetchState.mirroredFragmentIdentifier;
138 var environment = refetchEnvironment !== null && refetchEnvironment !== void 0 ? refetchEnvironment : parentEnvironment;
139 var QueryResource = getQueryResourceForEnvironment(environment);
140 var profilerContext = useContext(ProfilerContext);
141 var shouldReset = environment !== mirroredEnvironment || fragmentIdentifier !== mirroredFragmentIdentifier;
142
143 var _useMemoVariables = useMemoVariables(refetchVariables),
144 memoRefetchVariables = _useMemoVariables[0];
145
146 var refetchQuery = useMemo(function () {
147 return memoRefetchVariables != null ? createOperationDescriptor(refetchableRequest, memoRefetchVariables) : null;
148 }, [memoRefetchVariables, refetchableRequest]);
149 var refetchedQueryResult;
150 var fragmentRef = parentFragmentRef;
151
152 if (shouldReset) {
153 dispatch({
154 type: 'reset',
155 environment: environment,
156 fragmentIdentifier: fragmentIdentifier
157 });
158 } else if (refetchQuery != null) {
159 // check __typename/id is consistent if refetch existing data on Node
160 var debugPreviousIDAndTypename;
161
162 if (process.env.NODE_ENV !== "production") {
163 debugPreviousIDAndTypename = debugFunctions.getInitialIDAndType(memoRefetchVariables, fragmentRefPathInResponse, environment);
164 } // If refetch has been called, we read/fetch the refetch query here. If
165 // the refetch query hasn't been fetched or isn't cached, we will suspend
166 // at this point.
167
168
169 var _readQuery = readQuery(environment, refetchQuery, fetchPolicy, renderPolicy, refetchGeneration, componentDisplayName, {
170 start: startFetch,
171 complete: function complete(maybeError) {
172 completeFetch();
173 onComplete && onComplete(maybeError !== null && maybeError !== void 0 ? maybeError : null);
174
175 if (process.env.NODE_ENV !== "production") {
176 if (!maybeError) {
177 debugFunctions.checkSameTypeAfterRefetch(debugPreviousIDAndTypename, environment, fragmentNode, componentDisplayName);
178 }
179 }
180 }
181 }, profilerContext),
182 queryResult = _readQuery[0],
183 queryData = _readQuery[1];
184
185 refetchedQueryResult = queryResult; // After reading/fetching the refetch query, we extract from the
186 // refetch query response the new fragment ref we need to use to read
187 // the fragment. The new fragment ref will point to the refetch query
188 // as its fragment owner.
189
190 var refetchedFragmentRef = getValueAtPath(queryData, fragmentRefPathInResponse);
191 fragmentRef = refetchedFragmentRef;
192
193 if (process.env.NODE_ENV !== "production") {
194 debugFunctions.checkSameIDAfterRefetch(debugPreviousIDAndTypename, fragmentRef, fragmentNode, componentDisplayName);
195 }
196 } // We read and subscribe to the fragment using useFragmentNode.
197 // If refetch was called, we read the fragment using the new computed
198 // fragment ref from the refetch query response; otherwise, we use the
199 // fragment ref passed by the caller as normal.
200
201
202 var _useFragmentNode = useFragmentNode(fragmentNode, fragmentRef, componentDisplayName),
203 fragmentData = _useFragmentNode.data,
204 disableStoreUpdates = _useFragmentNode.disableStoreUpdates,
205 enableStoreUpdates = _useFragmentNode.enableStoreUpdates;
206
207 useEffect(function () {
208 // Retain the refetch query if it was fetched and release it
209 // in the useEffect cleanup.
210 var queryDisposable = refetchedQueryResult != null ? QueryResource.retain(refetchedQueryResult, profilerContext) : null;
211 return function () {
212 if (queryDisposable) {
213 queryDisposable.dispose();
214 }
215 }; // NOTE: We disable react-hooks-deps warning because
216 // refetchedQueryResult is captured by including refetchQuery, which is
217 // already capturing if the query or variables changed.
218 // eslint-disable-next-line react-hooks/exhaustive-deps
219 }, [QueryResource, fragmentIdentifier, refetchQuery, refetchGeneration]);
220 var refetch = useRefetchFunction(fragmentNode, parentFragmentRef, fragmentIdentifier, fragmentRefPathInResponse, fragmentData, dispatch, disposeFetch, componentDisplayName, identifierField);
221 return {
222 fragmentData: fragmentData,
223 fragmentRef: fragmentRef,
224 refetch: refetch,
225 disableStoreUpdates: disableStoreUpdates,
226 enableStoreUpdates: enableStoreUpdates
227 };
228}
229
230function useRefetchFunction(fragmentNode, parentFragmentRef, fragmentIdentifier, fragmentRefPathInResponse, fragmentData, dispatch, disposeFetch, componentDisplayName, identifierField) {
231 var isMountedRef = useIsMountedRef();
232 var identifierValue = identifierField != null && fragmentData != null && typeof fragmentData === 'object' ? fragmentData[identifierField] : null;
233 return useCallback(function (providedRefetchVariables, options) {
234 // Bail out and warn if we're trying to refetch after the component
235 // has unmounted
236 if (isMountedRef.current !== true) {
237 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `refetch` 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;
238 return {
239 dispose: function dispose() {}
240 };
241 }
242
243 if (Scheduler.unstable_getCurrentPriorityLevel() < Scheduler.unstable_NormalPriority) {
244 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `refetch` 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.', fragmentNode.name, componentDisplayName) : void 0;
245 }
246
247 if (parentFragmentRef == null) {
248 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `refetch` while using a null fragment ref ' + 'for fragment `%s` in `%s`. When calling `refetch`, we expect ' + "initial fragment data to be non-null. Please make sure you're " + 'passing a valid fragment ref to `%s` before calling ' + '`refetch`, or make sure you pass all required variables to `refetch`.', fragmentNode.name, componentDisplayName, componentDisplayName) : void 0;
249 }
250
251 var environment = options === null || options === void 0 ? void 0 : options.__environment;
252 var fetchPolicy = options === null || options === void 0 ? void 0 : options.fetchPolicy;
253 var renderPolicy = options === null || options === void 0 ? void 0 : options.UNSTABLE_renderPolicy;
254 var onComplete = options === null || options === void 0 ? void 0 : options.onComplete;
255 var fragmentSelector = getSelector(fragmentNode, parentFragmentRef);
256 var parentVariables;
257 var fragmentVariables;
258
259 if (fragmentSelector == null) {
260 parentVariables = {};
261 fragmentVariables = {};
262 } else if (fragmentSelector.kind === 'PluralReaderSelector') {
263 var _fragmentSelector$sel, _fragmentSelector$sel2, _fragmentSelector$sel3, _fragmentSelector$sel4;
264
265 parentVariables = (_fragmentSelector$sel = (_fragmentSelector$sel2 = fragmentSelector.selectors[0]) === null || _fragmentSelector$sel2 === void 0 ? void 0 : _fragmentSelector$sel2.owner.variables) !== null && _fragmentSelector$sel !== void 0 ? _fragmentSelector$sel : {};
266 fragmentVariables = (_fragmentSelector$sel3 = (_fragmentSelector$sel4 = fragmentSelector.selectors[0]) === null || _fragmentSelector$sel4 === void 0 ? void 0 : _fragmentSelector$sel4.variables) !== null && _fragmentSelector$sel3 !== void 0 ? _fragmentSelector$sel3 : {};
267 } else {
268 parentVariables = fragmentSelector.owner.variables;
269 fragmentVariables = fragmentSelector.variables;
270 } // NOTE: A user of `useRefetchableFragment()` may pass a subset of
271 // all variables required by the fragment when calling `refetch()`.
272 // We fill in any variables not passed by the call to `refetch()` with the
273 // variables from the original parent fragment owner.
274
275 /* $FlowFixMe[cannot-spread-indexer] (>=0.123.0) This comment suppresses
276 * an error found when Flow v0.123.0 was deployed. To see the error
277 * delete this comment and run Flow. */
278
279
280 var refetchVariables = _objectSpread({}, parentVariables, {}, fragmentVariables, {}, providedRefetchVariables); // If the query needs an identifier value ('id' or similar) and one
281 // was not explicitly provided, read it from the fragment data.
282
283
284 if (identifierField != null && !providedRefetchVariables.hasOwnProperty('id')) {
285 // @refetchable fragments are guaranteed to have an `id` selection
286 // if the type is Node, implements Node, or is @fetchable. Double-check
287 // that there actually is a value at runtime.
288 if (typeof identifierValue !== 'string') {
289 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected result to have a string ' + '`%s` in order to refetch, got `%s`.', identifierField, identifierValue) : void 0;
290 }
291
292 refetchVariables.id = identifierValue;
293 }
294
295 dispatch({
296 type: 'refetch',
297 refetchVariables: refetchVariables,
298 fetchPolicy: fetchPolicy,
299 renderPolicy: renderPolicy,
300 onComplete: onComplete,
301 environment: environment
302 });
303 return {
304 dispose: disposeFetch
305 };
306 }, // NOTE: We disable react-hooks-deps warning because:
307 // - We know fragmentRefPathInResponse is static, so it can be omitted from
308 // deps
309 // - We know fragmentNode is static, so it can be omitted from deps.
310 // - fragmentNode and parentFragmentRef are also captured by including
311 // fragmentIdentifier
312 // eslint-disable-next-line react-hooks/exhaustive-deps
313 [fragmentIdentifier, dispatch, disposeFetch, identifierValue]);
314}
315
316function readQuery(environment, query, fetchPolicy, renderPolicy, refetchGeneration, componentDisplayName, _ref, profilerContext) {
317 var start = _ref.start,
318 complete = _ref.complete;
319 var QueryResource = getQueryResourceForEnvironment(environment);
320 var FragmentResource = getFragmentResourceForEnvironment(environment);
321 var queryResult = profilerContext.wrapPrepareQueryResource(function () {
322 return QueryResource.prepare(query, fetchQuery(environment, query, {
323 networkCacheConfig: {
324 force: true
325 }
326 }), fetchPolicy, renderPolicy, {
327 start: start,
328 error: complete,
329 complete: complete
330 }, // NOTE: QueryResource will keep a cache entry for a query for the
331 // entire lifetime of this component. However, every time refetch is
332 // called, we want to make sure we correctly attempt to fetch the query
333 // (taking into account the fetchPolicy), even if we're refetching the exact
334 // same query (e.g. refreshing it).
335 // To do so, we keep track of every time refetch is called with
336 // `refetchGenerationRef`, which we can use as a key for the query in
337 // QueryResource.
338 refetchGeneration);
339 });
340 var queryData = FragmentResource.read(queryResult.fragmentNode, queryResult.fragmentRef, componentDisplayName).data;
341 !(queryData != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected to be able to read refetch query response. ' + "If you're seeing this, this is likely a bug in Relay.") : invariant(false) : void 0;
342 return [queryResult, queryData];
343}
344
345var debugFunctions;
346
347if (process.env.NODE_ENV !== "production") {
348 debugFunctions = {
349 getInitialIDAndType: function getInitialIDAndType(memoRefetchVariables, fragmentRefPathInResponse, environment) {
350 var _require5 = require('relay-runtime'),
351 Record = _require5.Record;
352
353 var id = memoRefetchVariables === null || memoRefetchVariables === void 0 ? void 0 : memoRefetchVariables.id;
354
355 if (fragmentRefPathInResponse.length !== 1 || fragmentRefPathInResponse[0] !== 'node' || id == null) {
356 return null;
357 }
358
359 var recordSource = environment.getStore().getSource();
360 var record = recordSource.get(id);
361 var typename = record && Record.getType(record);
362
363 if (typename == null) {
364 return null;
365 }
366
367 return {
368 id: id,
369 typename: typename
370 };
371 },
372 checkSameTypeAfterRefetch: function checkSameTypeAfterRefetch(previousIDAndType, environment, fragmentNode, componentDisplayName) {
373 var _require6 = require('relay-runtime'),
374 Record = _require6.Record;
375
376 if (!previousIDAndType) {
377 return;
378 }
379
380 var recordSource = environment.getStore().getSource();
381 var record = recordSource.get(previousIDAndType.id);
382 var typename = record && Record.getType(record);
383
384 if (typename !== previousIDAndType.typename) {
385 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Call to `refetch` returned data with a different ' + '__typename: was `%s`, now `%s`, on `%s` in `%s`. ' + 'Please make sure the server correctly implements' + 'unique id requirement.', previousIDAndType.typename, typename, fragmentNode.name, componentDisplayName) : void 0;
386 }
387 },
388 checkSameIDAfterRefetch: function checkSameIDAfterRefetch(previousIDAndTypename, refetchedFragmentRef, fragmentNode, componentDisplayName) {
389 if (previousIDAndTypename == null) {
390 return;
391 }
392
393 var _require7 = require('relay-runtime'),
394 ID_KEY = _require7.ID_KEY; // $FlowExpectedError[incompatible-use]
395
396
397 var resultID = refetchedFragmentRef[ID_KEY];
398
399 if (resultID != null && resultID !== previousIDAndTypename.id) {
400 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Call to `refetch` returned a different id, expected ' + '`%s`, got `%s`, on `%s` in `%s`. ' + 'Please make sure the server correctly implements ' + 'unique id requirement.', resultID, previousIDAndTypename.id, fragmentNode.name, componentDisplayName) : void 0;
401 }
402 }
403 };
404}
405
406module.exports = useRefetchableFragmentNode;
\No newline at end of file