UNPKG

react-query

Version:

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

566 lines (442 loc) 18.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.QueryObserver = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose")); var _utils = require("./utils"); var _notifyManager = require("./notifyManager"); var _focusManager = require("./focusManager"); var _subscribable = require("./subscribable"); var _logger = require("./logger"); var _retryer = require("./retryer"); var QueryObserver = /*#__PURE__*/function (_Subscribable) { (0, _inheritsLoose2.default)(QueryObserver, _Subscribable); function QueryObserver(client, options) { var _this; _this = _Subscribable.call(this) || this; _this.client = client; _this.options = options; _this.trackedProps = []; _this.selectError = null; _this.bindMethods(); _this.setOptions(options); return _this; } var _proto = QueryObserver.prototype; _proto.bindMethods = function bindMethods() { this.remove = this.remove.bind(this); this.refetch = this.refetch.bind(this); }; _proto.onSubscribe = function onSubscribe() { if (this.listeners.length === 1) { this.currentQuery.addObserver(this); if (shouldFetchOnMount(this.currentQuery, this.options)) { this.executeFetch(); } this.updateTimers(); } }; _proto.onUnsubscribe = function onUnsubscribe() { if (!this.listeners.length) { this.destroy(); } }; _proto.shouldFetchOnReconnect = function shouldFetchOnReconnect() { return shouldFetchOn(this.currentQuery, this.options, this.options.refetchOnReconnect); }; _proto.shouldFetchOnWindowFocus = function shouldFetchOnWindowFocus() { return shouldFetchOn(this.currentQuery, this.options, this.options.refetchOnWindowFocus); }; _proto.destroy = function destroy() { this.listeners = []; this.clearTimers(); this.currentQuery.removeObserver(this); }; _proto.setOptions = function setOptions(options, notifyOptions) { var prevOptions = this.options; var prevQuery = this.currentQuery; this.options = this.client.defaultQueryObserverOptions(options); if (typeof this.options.enabled !== 'undefined' && typeof this.options.enabled !== 'boolean') { throw new Error('Expected enabled to be a boolean'); } // Keep previous query key if the user does not supply one if (!this.options.queryKey) { this.options.queryKey = prevOptions.queryKey; } this.updateQuery(); var mounted = this.hasListeners(); // Fetch if there are subscribers if (mounted && shouldFetchOptionally(this.currentQuery, prevQuery, this.options, prevOptions)) { this.executeFetch(); } // Update result this.updateResult(notifyOptions); // Update stale interval if needed if (mounted && (this.currentQuery !== prevQuery || this.options.enabled !== prevOptions.enabled || this.options.staleTime !== prevOptions.staleTime)) { this.updateStaleTimeout(); } var nextRefetchInterval = this.computeRefetchInterval(); // Update refetch interval if needed if (mounted && (this.currentQuery !== prevQuery || this.options.enabled !== prevOptions.enabled || nextRefetchInterval !== this.currentRefetchInterval)) { this.updateRefetchInterval(nextRefetchInterval); } }; _proto.getOptimisticResult = function getOptimisticResult(options) { var defaultedOptions = this.client.defaultQueryObserverOptions(options); var query = this.client.getQueryCache().build(this.client, defaultedOptions); return this.createResult(query, defaultedOptions); }; _proto.getCurrentResult = function getCurrentResult() { return this.currentResult; }; _proto.trackResult = function trackResult(result, defaultedOptions) { var _this2 = this; var trackedResult = {}; var trackProp = function trackProp(key) { if (!_this2.trackedProps.includes(key)) { _this2.trackedProps.push(key); } }; Object.keys(result).forEach(function (key) { Object.defineProperty(trackedResult, key, { configurable: false, enumerable: true, get: function get() { trackProp(key); return result[key]; } }); }); if (defaultedOptions.useErrorBoundary || defaultedOptions.suspense) { trackProp('error'); } return trackedResult; }; _proto.getNextResult = function getNextResult(options) { var _this3 = this; return new Promise(function (resolve, reject) { var unsubscribe = _this3.subscribe(function (result) { if (!result.isFetching) { unsubscribe(); if (result.isError && (options == null ? void 0 : options.throwOnError)) { reject(result.error); } else { resolve(result); } } }); }); }; _proto.getCurrentQuery = function getCurrentQuery() { return this.currentQuery; }; _proto.remove = function remove() { this.client.getQueryCache().remove(this.currentQuery); }; _proto.refetch = function refetch(options) { return this.fetch((0, _extends2.default)({}, options, { meta: { refetchPage: options == null ? void 0 : options.refetchPage } })); }; _proto.fetchOptimistic = function fetchOptimistic(options) { var _this4 = this; var defaultedOptions = this.client.defaultQueryObserverOptions(options); var query = this.client.getQueryCache().build(this.client, defaultedOptions); return query.fetch().then(function () { return _this4.createResult(query, defaultedOptions); }); }; _proto.fetch = function fetch(fetchOptions) { var _this5 = this; return this.executeFetch(fetchOptions).then(function () { _this5.updateResult(); return _this5.currentResult; }); }; _proto.executeFetch = function executeFetch(fetchOptions) { // Make sure we reference the latest query as the current one might have been removed this.updateQuery(); // Fetch var promise = this.currentQuery.fetch(this.options, fetchOptions); if (!(fetchOptions == null ? void 0 : fetchOptions.throwOnError)) { promise = promise.catch(_utils.noop); } return promise; }; _proto.updateStaleTimeout = function updateStaleTimeout() { var _this6 = this; this.clearStaleTimeout(); if (_utils.isServer || this.currentResult.isStale || !(0, _utils.isValidTimeout)(this.options.staleTime)) { return; } var time = (0, _utils.timeUntilStale)(this.currentResult.dataUpdatedAt, this.options.staleTime); // The timeout is sometimes triggered 1 ms before the stale time expiration. // To mitigate this issue we always add 1 ms to the timeout. var timeout = time + 1; this.staleTimeoutId = setTimeout(function () { if (!_this6.currentResult.isStale) { _this6.updateResult(); } }, timeout); }; _proto.computeRefetchInterval = function computeRefetchInterval() { var _this$options$refetch; return typeof this.options.refetchInterval === 'function' ? this.options.refetchInterval(this.currentResult.data, this.currentQuery) : (_this$options$refetch = this.options.refetchInterval) != null ? _this$options$refetch : false; }; _proto.updateRefetchInterval = function updateRefetchInterval(nextInterval) { var _this7 = this; this.clearRefetchInterval(); this.currentRefetchInterval = nextInterval; if (_utils.isServer || this.options.enabled === false || !(0, _utils.isValidTimeout)(this.currentRefetchInterval) || this.currentRefetchInterval === 0) { return; } this.refetchIntervalId = setInterval(function () { if (_this7.options.refetchIntervalInBackground || _focusManager.focusManager.isFocused()) { _this7.executeFetch(); } }, this.currentRefetchInterval); }; _proto.updateTimers = function updateTimers() { this.updateStaleTimeout(); this.updateRefetchInterval(this.computeRefetchInterval()); }; _proto.clearTimers = function clearTimers() { this.clearStaleTimeout(); this.clearRefetchInterval(); }; _proto.clearStaleTimeout = function clearStaleTimeout() { if (this.staleTimeoutId) { clearTimeout(this.staleTimeoutId); this.staleTimeoutId = undefined; } }; _proto.clearRefetchInterval = function clearRefetchInterval() { if (this.refetchIntervalId) { clearInterval(this.refetchIntervalId); this.refetchIntervalId = undefined; } }; _proto.createResult = function createResult(query, options) { var prevQuery = this.currentQuery; var prevOptions = this.options; var prevResult = this.currentResult; var prevResultState = this.currentResultState; var prevResultOptions = this.currentResultOptions; var queryChange = query !== prevQuery; var queryInitialState = queryChange ? query.state : this.currentQueryInitialState; var prevQueryResult = queryChange ? this.currentResult : this.previousQueryResult; var state = query.state; var dataUpdatedAt = state.dataUpdatedAt, error = state.error, errorUpdatedAt = state.errorUpdatedAt, isFetching = state.isFetching, status = state.status; var isPreviousData = false; var isPlaceholderData = false; var data; // Optimistically set result in fetching state if needed if (options.optimisticResults) { var mounted = this.hasListeners(); var fetchOnMount = !mounted && shouldFetchOnMount(query, options); var fetchOptionally = mounted && shouldFetchOptionally(query, prevQuery, options, prevOptions); if (fetchOnMount || fetchOptionally) { isFetching = true; if (!dataUpdatedAt) { status = 'loading'; } } } // Keep previous data if needed if (options.keepPreviousData && !state.dataUpdateCount && (prevQueryResult == null ? void 0 : prevQueryResult.isSuccess) && status !== 'error') { data = prevQueryResult.data; dataUpdatedAt = prevQueryResult.dataUpdatedAt; status = prevQueryResult.status; isPreviousData = true; } // Select data if needed else if (options.select && typeof state.data !== 'undefined') { // Memoize select result if (prevResult && state.data === (prevResultState == null ? void 0 : prevResultState.data) && options.select === this.selectFn) { data = this.selectResult; } else { try { this.selectFn = options.select; data = options.select(state.data); if (options.structuralSharing !== false) { data = (0, _utils.replaceEqualDeep)(prevResult == null ? void 0 : prevResult.data, data); } this.selectResult = data; this.selectError = null; } catch (selectError) { (0, _logger.getLogger)().error(selectError); this.selectError = selectError; } } } // Use query data else { data = state.data; } // Show placeholder data if needed if (typeof options.placeholderData !== 'undefined' && typeof data === 'undefined' && (status === 'loading' || status === 'idle')) { var placeholderData; // Memoize placeholder data if ((prevResult == null ? void 0 : prevResult.isPlaceholderData) && options.placeholderData === (prevResultOptions == null ? void 0 : prevResultOptions.placeholderData)) { placeholderData = prevResult.data; } else { placeholderData = typeof options.placeholderData === 'function' ? options.placeholderData() : options.placeholderData; if (options.select && typeof placeholderData !== 'undefined') { try { placeholderData = options.select(placeholderData); if (options.structuralSharing !== false) { placeholderData = (0, _utils.replaceEqualDeep)(prevResult == null ? void 0 : prevResult.data, placeholderData); } this.selectError = null; } catch (selectError) { (0, _logger.getLogger)().error(selectError); this.selectError = selectError; } } } if (typeof placeholderData !== 'undefined') { status = 'success'; data = placeholderData; isPlaceholderData = true; } } if (this.selectError) { error = this.selectError; data = this.selectResult; errorUpdatedAt = Date.now(); status = 'error'; } var result = { status: status, isLoading: status === 'loading', isSuccess: status === 'success', isError: status === 'error', isIdle: status === 'idle', data: data, dataUpdatedAt: dataUpdatedAt, error: error, errorUpdatedAt: errorUpdatedAt, failureCount: state.fetchFailureCount, errorUpdateCount: state.errorUpdateCount, isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0, isFetchedAfterMount: state.dataUpdateCount > queryInitialState.dataUpdateCount || state.errorUpdateCount > queryInitialState.errorUpdateCount, isFetching: isFetching, isRefetching: isFetching && status !== 'loading', isLoadingError: status === 'error' && state.dataUpdatedAt === 0, isPlaceholderData: isPlaceholderData, isPreviousData: isPreviousData, isRefetchError: status === 'error' && state.dataUpdatedAt !== 0, isStale: isStale(query, options), refetch: this.refetch, remove: this.remove }; return result; }; _proto.shouldNotifyListeners = function shouldNotifyListeners(result, prevResult) { if (!prevResult) { return true; } var _this$options = this.options, notifyOnChangeProps = _this$options.notifyOnChangeProps, notifyOnChangePropsExclusions = _this$options.notifyOnChangePropsExclusions; if (!notifyOnChangeProps && !notifyOnChangePropsExclusions) { return true; } if (notifyOnChangeProps === 'tracked' && !this.trackedProps.length) { return true; } var includedProps = notifyOnChangeProps === 'tracked' ? this.trackedProps : notifyOnChangeProps; return Object.keys(result).some(function (key) { var typedKey = key; var changed = result[typedKey] !== prevResult[typedKey]; var isIncluded = includedProps == null ? void 0 : includedProps.some(function (x) { return x === key; }); var isExcluded = notifyOnChangePropsExclusions == null ? void 0 : notifyOnChangePropsExclusions.some(function (x) { return x === key; }); return changed && !isExcluded && (!includedProps || isIncluded); }); }; _proto.updateResult = function updateResult(notifyOptions) { var prevResult = this.currentResult; this.currentResult = this.createResult(this.currentQuery, this.options); this.currentResultState = this.currentQuery.state; this.currentResultOptions = this.options; // Only notify if something has changed if ((0, _utils.shallowEqualObjects)(this.currentResult, prevResult)) { return; } // Determine which callbacks to trigger var defaultNotifyOptions = { cache: true }; if ((notifyOptions == null ? void 0 : notifyOptions.listeners) !== false && this.shouldNotifyListeners(this.currentResult, prevResult)) { defaultNotifyOptions.listeners = true; } this.notify((0, _extends2.default)({}, defaultNotifyOptions, notifyOptions)); }; _proto.updateQuery = function updateQuery() { var query = this.client.getQueryCache().build(this.client, this.options); if (query === this.currentQuery) { return; } var prevQuery = this.currentQuery; this.currentQuery = query; this.currentQueryInitialState = query.state; this.previousQueryResult = this.currentResult; if (this.hasListeners()) { prevQuery == null ? void 0 : prevQuery.removeObserver(this); query.addObserver(this); } }; _proto.onQueryUpdate = function onQueryUpdate(action) { var notifyOptions = {}; if (action.type === 'success') { notifyOptions.onSuccess = true; } else if (action.type === 'error' && !(0, _retryer.isCancelledError)(action.error)) { notifyOptions.onError = true; } this.updateResult(notifyOptions); if (this.hasListeners()) { this.updateTimers(); } }; _proto.notify = function notify(notifyOptions) { var _this8 = this; _notifyManager.notifyManager.batch(function () { // First trigger the configuration callbacks if (notifyOptions.onSuccess) { _this8.options.onSuccess == null ? void 0 : _this8.options.onSuccess(_this8.currentResult.data); _this8.options.onSettled == null ? void 0 : _this8.options.onSettled(_this8.currentResult.data, null); } else if (notifyOptions.onError) { _this8.options.onError == null ? void 0 : _this8.options.onError(_this8.currentResult.error); _this8.options.onSettled == null ? void 0 : _this8.options.onSettled(undefined, _this8.currentResult.error); } // Then trigger the listeners if (notifyOptions.listeners) { _this8.listeners.forEach(function (listener) { listener(_this8.currentResult); }); } // Then the cache listeners if (notifyOptions.cache) { _this8.client.getQueryCache().notify({ query: _this8.currentQuery, type: 'observerResultsUpdated' }); } }); }; return QueryObserver; }(_subscribable.Subscribable); exports.QueryObserver = QueryObserver; function shouldLoadOnMount(query, options) { return options.enabled !== false && !query.state.dataUpdatedAt && !(query.state.status === 'error' && options.retryOnMount === false); } function shouldFetchOnMount(query, options) { return shouldLoadOnMount(query, options) || query.state.dataUpdatedAt > 0 && shouldFetchOn(query, options, options.refetchOnMount); } function shouldFetchOn(query, options, field) { if (options.enabled !== false) { var value = typeof field === 'function' ? field(query) : field; return value === 'always' || value !== false && isStale(query, options); } return false; } function shouldFetchOptionally(query, prevQuery, options, prevOptions) { return options.enabled !== false && (query !== prevQuery || prevOptions.enabled === false) && (!options.suspense || query.state.status !== 'error') && isStale(query, options); } function isStale(query, options) { return query.isStaleByTime(options.staleTime); }