UNPKG

react-query

Version:

Hooks for managing, caching and syncing asynchronous and remote data in React

460 lines (371 loc) 13.4 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { functionalUpdate, isValidTimeout, noop, replaceEqualDeep, timeUntilStale, ensureQueryKeyArray } from './utils'; import { notifyManager } from './notifyManager'; import { getLogger } from './logger'; import { Retryer, isCancelledError } from './retryer'; // TYPES // CLASS export var Query = /*#__PURE__*/function () { function Query(config) { this.defaultOptions = config.defaultOptions; this.setOptions(config.options); this.observers = []; this.cache = config.cache; this.queryKey = config.queryKey; this.queryHash = config.queryHash; this.initialState = config.state || this.getDefaultState(this.options); this.state = this.initialState; this.scheduleGc(); } var _proto = Query.prototype; _proto.setOptions = function setOptions(options) { var _this$options$cacheTi; this.options = _extends({}, this.defaultOptions, options); // Default to 5 minutes if not cache time is set this.cacheTime = Math.max(this.cacheTime || 0, (_this$options$cacheTi = this.options.cacheTime) != null ? _this$options$cacheTi : 5 * 60 * 1000); }; _proto.setDefaultOptions = function setDefaultOptions(options) { this.defaultOptions = options; }; _proto.scheduleGc = function scheduleGc() { var _this = this; this.clearGcTimeout(); if (isValidTimeout(this.cacheTime)) { this.gcTimeout = setTimeout(function () { _this.optionalRemove(); }, this.cacheTime); } }; _proto.clearGcTimeout = function clearGcTimeout() { clearTimeout(this.gcTimeout); this.gcTimeout = undefined; }; _proto.optionalRemove = function optionalRemove() { if (!this.observers.length && !this.state.isFetching) { this.cache.remove(this); } }; _proto.setData = function setData(updater, options) { var _this$options$isDataE, _this$options; var prevData = this.state.data; // Get the new data var data = functionalUpdate(updater, prevData); // Use prev data if an isDataEqual function is defined and returns `true` if ((_this$options$isDataE = (_this$options = this.options).isDataEqual) == null ? void 0 : _this$options$isDataE.call(_this$options, prevData, data)) { data = prevData; } else if (this.options.structuralSharing !== false) { // Structurally share data between prev and new data if needed data = replaceEqualDeep(prevData, data); } // Set data and mark it as cached this.dispatch({ data: data, type: 'success', dataUpdatedAt: options == null ? void 0 : options.updatedAt }); return data; }; _proto.setState = function setState(state, setStateOptions) { this.dispatch({ type: 'setState', state: state, setStateOptions: setStateOptions }); }; _proto.cancel = function cancel(options) { var _this$retryer; var promise = this.promise; (_this$retryer = this.retryer) == null ? void 0 : _this$retryer.cancel(options); return promise ? promise.then(noop).catch(noop) : Promise.resolve(); }; _proto.destroy = function destroy() { this.clearGcTimeout(); this.cancel({ silent: true }); }; _proto.reset = function reset() { this.destroy(); this.setState(this.initialState); }; _proto.isActive = function isActive() { return this.observers.some(function (observer) { return observer.options.enabled !== false; }); }; _proto.isFetching = function isFetching() { return this.state.isFetching; }; _proto.isStale = function isStale() { return this.state.isInvalidated || !this.state.dataUpdatedAt || this.observers.some(function (observer) { return observer.getCurrentResult().isStale; }); }; _proto.isStaleByTime = function isStaleByTime(staleTime) { if (staleTime === void 0) { staleTime = 0; } return this.state.isInvalidated || !this.state.dataUpdatedAt || !timeUntilStale(this.state.dataUpdatedAt, staleTime); }; _proto.onFocus = function onFocus() { var _this$retryer2; var observer = this.observers.find(function (x) { return x.shouldFetchOnWindowFocus(); }); if (observer) { observer.refetch(); } // Continue fetch if currently paused (_this$retryer2 = this.retryer) == null ? void 0 : _this$retryer2.continue(); }; _proto.onOnline = function onOnline() { var _this$retryer3; var observer = this.observers.find(function (x) { return x.shouldFetchOnReconnect(); }); if (observer) { observer.refetch(); } // Continue fetch if currently paused (_this$retryer3 = this.retryer) == null ? void 0 : _this$retryer3.continue(); }; _proto.addObserver = function addObserver(observer) { if (this.observers.indexOf(observer) === -1) { this.observers.push(observer); // Stop the query from being garbage collected this.clearGcTimeout(); this.cache.notify({ type: 'observerAdded', query: this, observer: observer }); } }; _proto.removeObserver = function removeObserver(observer) { if (this.observers.indexOf(observer) !== -1) { this.observers = this.observers.filter(function (x) { return x !== observer; }); if (!this.observers.length) { // If the transport layer does not support cancellation // we'll let the query continue so the result can be cached if (this.retryer) { if (this.retryer.isTransportCancelable) { this.retryer.cancel({ revert: true }); } else { this.retryer.cancelRetry(); } } if (this.cacheTime) { this.scheduleGc(); } else { this.cache.remove(this); } } this.cache.notify({ type: 'observerRemoved', query: this, observer: observer }); } }; _proto.getObserversCount = function getObserversCount() { return this.observers.length; }; _proto.invalidate = function invalidate() { if (!this.state.isInvalidated) { this.dispatch({ type: 'invalidate' }); } }; _proto.fetch = function fetch(options, fetchOptions) { var _this2 = this, _this$options$behavio, _context$fetchOptions; if (this.state.isFetching) { if (this.state.dataUpdatedAt && (fetchOptions == null ? void 0 : fetchOptions.cancelRefetch)) { // Silently cancel current fetch if the user wants to cancel refetches this.cancel({ silent: true }); } else if (this.promise) { // Return current promise if we are already fetching return this.promise; } } // Update config if passed, otherwise the config from the last execution is used if (options) { this.setOptions(options); } // Use the options from the first observer with a query function if no function is found. // This can happen when the query is hydrated or created with setQueryData. if (!this.options.queryFn) { var observer = this.observers.find(function (x) { return x.options.queryFn; }); if (observer) { this.setOptions(observer.options); } } var queryKey = ensureQueryKeyArray(this.queryKey); // Create query function context var queryFnContext = { queryKey: queryKey, pageParam: undefined }; // Create fetch function var fetchFn = function fetchFn() { return _this2.options.queryFn ? _this2.options.queryFn(queryFnContext) : Promise.reject('Missing queryFn'); }; // Trigger behavior hook var context = { fetchOptions: fetchOptions, options: this.options, queryKey: queryKey, state: this.state, fetchFn: fetchFn }; if ((_this$options$behavio = this.options.behavior) == null ? void 0 : _this$options$behavio.onFetch) { var _this$options$behavio2; (_this$options$behavio2 = this.options.behavior) == null ? void 0 : _this$options$behavio2.onFetch(context); } // Store state in case the current fetch needs to be reverted this.revertState = this.state; // Set to fetching state if not already in it if (!this.state.isFetching || this.state.fetchMeta !== ((_context$fetchOptions = context.fetchOptions) == null ? void 0 : _context$fetchOptions.meta)) { var _context$fetchOptions2; this.dispatch({ type: 'fetch', meta: (_context$fetchOptions2 = context.fetchOptions) == null ? void 0 : _context$fetchOptions2.meta }); } // Try to fetch the data this.retryer = new Retryer({ fn: context.fetchFn, onSuccess: function onSuccess(data) { _this2.setData(data); // Notify cache callback _this2.cache.config.onSuccess == null ? void 0 : _this2.cache.config.onSuccess(data, _this2); // Remove query after fetching if cache time is 0 if (_this2.cacheTime === 0) { _this2.optionalRemove(); } }, onError: function onError(error) { // Optimistically update state if needed if (!(isCancelledError(error) && error.silent)) { _this2.dispatch({ type: 'error', error: error }); } if (!isCancelledError(error)) { // Notify cache callback _this2.cache.config.onError == null ? void 0 : _this2.cache.config.onError(error, _this2); // Log error getLogger().error(error); } // Remove query after fetching if cache time is 0 if (_this2.cacheTime === 0) { _this2.optionalRemove(); } }, onFail: function onFail() { _this2.dispatch({ type: 'failed' }); }, onPause: function onPause() { _this2.dispatch({ type: 'pause' }); }, onContinue: function onContinue() { _this2.dispatch({ type: 'continue' }); }, retry: context.options.retry, retryDelay: context.options.retryDelay }); this.promise = this.retryer.promise; return this.promise; }; _proto.dispatch = function dispatch(action) { var _this3 = this; this.state = this.reducer(this.state, action); notifyManager.batch(function () { _this3.observers.forEach(function (observer) { observer.onQueryUpdate(action); }); _this3.cache.notify({ query: _this3, type: 'queryUpdated', action: action }); }); }; _proto.getDefaultState = function getDefaultState(options) { var data = typeof options.initialData === 'function' ? options.initialData() : options.initialData; var hasInitialData = typeof options.initialData !== 'undefined'; var initialDataUpdatedAt = hasInitialData ? typeof options.initialDataUpdatedAt === 'function' ? options.initialDataUpdatedAt() : options.initialDataUpdatedAt : 0; var hasData = typeof data !== 'undefined'; return { data: data, dataUpdateCount: 0, dataUpdatedAt: hasData ? initialDataUpdatedAt != null ? initialDataUpdatedAt : Date.now() : 0, error: null, errorUpdateCount: 0, errorUpdatedAt: 0, fetchFailureCount: 0, fetchMeta: null, isFetching: false, isInvalidated: false, isPaused: false, status: hasData ? 'success' : 'idle' }; }; _proto.reducer = function reducer(state, action) { var _action$meta, _action$dataUpdatedAt; switch (action.type) { case 'failed': return _extends({}, state, { fetchFailureCount: state.fetchFailureCount + 1 }); case 'pause': return _extends({}, state, { isPaused: true }); case 'continue': return _extends({}, state, { isPaused: false }); case 'fetch': return _extends({}, state, { fetchFailureCount: 0, fetchMeta: (_action$meta = action.meta) != null ? _action$meta : null, isFetching: true, isPaused: false, status: !state.dataUpdatedAt ? 'loading' : state.status }); case 'success': return _extends({}, state, { data: action.data, dataUpdateCount: state.dataUpdateCount + 1, dataUpdatedAt: (_action$dataUpdatedAt = action.dataUpdatedAt) != null ? _action$dataUpdatedAt : Date.now(), error: null, fetchFailureCount: 0, isFetching: false, isInvalidated: false, isPaused: false, status: 'success' }); case 'error': var error = action.error; if (isCancelledError(error) && error.revert && this.revertState) { return _extends({}, this.revertState); } return _extends({}, state, { error: error, errorUpdateCount: state.errorUpdateCount + 1, errorUpdatedAt: Date.now(), fetchFailureCount: state.fetchFailureCount + 1, isFetching: false, isPaused: false, status: 'error' }); case 'invalidate': return _extends({}, state, { isInvalidated: true }); case 'setState': return _extends({}, state, action.state); default: return state; } }; return Query; }();