727 lines
32 KiB
JavaScript
727 lines
32 KiB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
var React = require('react');
|
|
var shim = require('use-sync-external-store/shim');
|
|
var index_js = require('../_internal/index.js');
|
|
|
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
|
|
// Shared state between server components and client components
|
|
const noop = ()=>{};
|
|
// Using noop() as the undefined value as undefined can be replaced
|
|
// by something else. Prettier ignore and extra parentheses are necessary here
|
|
// to ensure that tsc doesn't remove the __NOINLINE__ comment.
|
|
// prettier-ignore
|
|
const UNDEFINED = /*#__NOINLINE__*/ noop();
|
|
const OBJECT = Object;
|
|
const isUndefined = (v)=>v === UNDEFINED;
|
|
const isFunction = (v)=>typeof v == 'function';
|
|
|
|
// use WeakMap to store the object->key mapping
|
|
// so the objects can be garbage collected.
|
|
// WeakMap uses a hashtable under the hood, so the lookup
|
|
// complexity is almost O(1).
|
|
const table = new WeakMap();
|
|
const getTypeName = (value)=>OBJECT.prototype.toString.call(value);
|
|
const isObjectTypeName = (typeName, type)=>typeName === `[object ${type}]`;
|
|
// counter of the key
|
|
let counter = 0;
|
|
// A stable hash implementation that supports:
|
|
// - Fast and ensures unique hash properties
|
|
// - Handles unserializable values
|
|
// - Handles object key ordering
|
|
// - Generates short results
|
|
//
|
|
// This is not a serialization function, and the result is not guaranteed to be
|
|
// parsable.
|
|
const stableHash = (arg)=>{
|
|
const type = typeof arg;
|
|
const typeName = getTypeName(arg);
|
|
const isDate = isObjectTypeName(typeName, 'Date');
|
|
const isRegex = isObjectTypeName(typeName, 'RegExp');
|
|
const isPlainObject = isObjectTypeName(typeName, 'Object');
|
|
let result;
|
|
let index;
|
|
if (OBJECT(arg) === arg && !isDate && !isRegex) {
|
|
// Object/function, not null/date/regexp. Use WeakMap to store the id first.
|
|
// If it's already hashed, directly return the result.
|
|
result = table.get(arg);
|
|
if (result) return result;
|
|
// Store the hash first for circular reference detection before entering the
|
|
// recursive `stableHash` calls.
|
|
// For other objects like set and map, we use this id directly as the hash.
|
|
result = ++counter + '~';
|
|
table.set(arg, result);
|
|
if (Array.isArray(arg)) {
|
|
// Array.
|
|
result = '@';
|
|
for(index = 0; index < arg.length; index++){
|
|
result += stableHash(arg[index]) + ',';
|
|
}
|
|
table.set(arg, result);
|
|
}
|
|
if (isPlainObject) {
|
|
// Object, sort keys.
|
|
result = '#';
|
|
const keys = OBJECT.keys(arg).sort();
|
|
while(!isUndefined(index = keys.pop())){
|
|
if (!isUndefined(arg[index])) {
|
|
result += index + ':' + stableHash(arg[index]) + ',';
|
|
}
|
|
}
|
|
table.set(arg, result);
|
|
}
|
|
} else {
|
|
result = isDate ? arg.toJSON() : type == 'symbol' ? arg.toString() : type == 'string' ? JSON.stringify(arg) : '' + arg;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const serialize = (key)=>{
|
|
if (isFunction(key)) {
|
|
try {
|
|
key = key();
|
|
} catch (err) {
|
|
// dependencies not ready
|
|
key = '';
|
|
}
|
|
}
|
|
// Use the original key as the argument of fetcher. This can be a string or an
|
|
// array of values.
|
|
const args = key;
|
|
// If key is not falsy, or not an empty array, hash it.
|
|
key = typeof key == 'string' ? key : (Array.isArray(key) ? key.length : key) ? stableHash(key) : '';
|
|
return [
|
|
key,
|
|
args
|
|
];
|
|
};
|
|
|
|
const unstable_serialize = (key)=>serialize(key)[0];
|
|
|
|
/// <reference types="react/experimental" />
|
|
const use = React__default.default.use || // This extra generic is to avoid TypeScript mixing up the generic and JSX sytax
|
|
// and emitting an error.
|
|
// We assume that this is only for the `use(thenable)` case, not `use(context)`.
|
|
// https://github.com/facebook/react/blob/aed00dacfb79d17c53218404c52b1c7aa59c4a89/packages/react-server/src/ReactFizzThenable.js#L45
|
|
((thenable)=>{
|
|
switch(thenable.status){
|
|
case 'pending':
|
|
throw thenable;
|
|
case 'fulfilled':
|
|
return thenable.value;
|
|
case 'rejected':
|
|
throw thenable.reason;
|
|
default:
|
|
thenable.status = 'pending';
|
|
thenable.then((v)=>{
|
|
thenable.status = 'fulfilled';
|
|
thenable.value = v;
|
|
}, (e)=>{
|
|
thenable.status = 'rejected';
|
|
thenable.reason = e;
|
|
});
|
|
throw thenable;
|
|
}
|
|
});
|
|
const WITH_DEDUPE = {
|
|
dedupe: true
|
|
};
|
|
const resolvedUndef = Promise.resolve(index_js.UNDEFINED);
|
|
const sub = ()=>index_js.noop;
|
|
/**
|
|
* The core implementation of the useSWR hook.
|
|
*
|
|
* This is the main handler function that implements all SWR functionality including
|
|
* data fetching, caching, revalidation, error handling, and state management.
|
|
* It manages the complete lifecycle of SWR requests from initialization through
|
|
* cleanup.
|
|
*
|
|
* Key responsibilities:
|
|
* - Key serialization and normalization
|
|
* - Cache state management and synchronization
|
|
* - Automatic and manual revalidation
|
|
* - Error handling and retry logic
|
|
* - Suspense integration
|
|
* - Loading state management
|
|
* - Effect cleanup and memory management
|
|
*
|
|
* @template Data - The type of data returned by the fetcher
|
|
* @template Error - The type of error that can be thrown
|
|
*
|
|
* @param _key - The SWR key (string, array, object, function, or falsy)
|
|
* @param fetcher - The fetcher function to retrieve data, or null to disable fetching
|
|
* @param config - Complete SWR configuration object with both public and internal options
|
|
*
|
|
* @returns SWRResponse object containing data, error, mutate function, and loading states
|
|
*
|
|
* @internal This is the internal implementation. Use `useSWR` instead.
|
|
*/ const useSWRHandler = (_key, fetcher, config)=>{
|
|
const { cache, compare, suspense, fallbackData, revalidateOnMount, revalidateIfStale, refreshInterval, refreshWhenHidden, refreshWhenOffline, keepPreviousData, strictServerPrefetchWarning } = config;
|
|
const [EVENT_REVALIDATORS, MUTATION, FETCH, PRELOAD] = index_js.SWRGlobalState.get(cache);
|
|
// `key` is the identifier of the SWR internal state,
|
|
// `fnArg` is the argument/arguments parsed from the key, which will be passed
|
|
// to the fetcher.
|
|
// All of them are derived from `_key`.
|
|
const [key, fnArg] = index_js.serialize(_key);
|
|
// If it's the initial render of this hook.
|
|
const initialMountedRef = React.useRef(false);
|
|
// If the hook is unmounted already. This will be used to prevent some effects
|
|
// to be called after unmounting.
|
|
const unmountedRef = React.useRef(false);
|
|
// Refs to keep the key and config.
|
|
const keyRef = React.useRef(key);
|
|
const fetcherRef = React.useRef(fetcher);
|
|
const configRef = React.useRef(config);
|
|
const getConfig = ()=>configRef.current;
|
|
const isActive = ()=>getConfig().isVisible() && getConfig().isOnline();
|
|
const [getCache, setCache, subscribeCache, getInitialCache] = index_js.createCacheHelper(cache, key);
|
|
const stateDependencies = React.useRef({}).current;
|
|
// Resolve the fallback data from either the inline option, or the global provider.
|
|
// If it's a promise, we simply let React suspend and resolve it for us.
|
|
const fallback = index_js.isUndefined(fallbackData) ? index_js.isUndefined(config.fallback) ? index_js.UNDEFINED : config.fallback[key] : fallbackData;
|
|
const isEqual = (prev, current)=>{
|
|
for(const _ in stateDependencies){
|
|
const t = _;
|
|
if (t === 'data') {
|
|
if (!compare(prev[t], current[t])) {
|
|
if (!index_js.isUndefined(prev[t])) {
|
|
return false;
|
|
}
|
|
if (!compare(returnedData, current[t])) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (current[t] !== prev[t]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
const isInitialMount = !initialMountedRef.current;
|
|
const getSnapshot = React.useMemo(()=>{
|
|
const cachedData = getCache();
|
|
const initialData = getInitialCache();
|
|
const getSelectedCache = (state)=>{
|
|
// We only select the needed fields from the state.
|
|
const snapshot = index_js.mergeObjects(state);
|
|
delete snapshot._k;
|
|
const shouldStartRequest = (()=>{
|
|
if (!key) return false;
|
|
if (!fetcher) return false;
|
|
// If it's paused, we skip revalidation.
|
|
if (getConfig().isPaused()) return false;
|
|
// If `revalidateOnMount` is set, we take the value directly.
|
|
if (isInitialMount && !index_js.isUndefined(revalidateOnMount)) return revalidateOnMount;
|
|
const data = !index_js.isUndefined(fallback) ? fallback : snapshot.data;
|
|
if (suspense) return index_js.isUndefined(data) || revalidateIfStale;
|
|
return index_js.isUndefined(data) || revalidateIfStale;
|
|
})();
|
|
if (!shouldStartRequest) {
|
|
return snapshot;
|
|
}
|
|
return {
|
|
isValidating: true,
|
|
isLoading: true,
|
|
...snapshot
|
|
};
|
|
};
|
|
const clientSnapshot = getSelectedCache(cachedData);
|
|
const serverSnapshot = cachedData === initialData ? clientSnapshot : getSelectedCache(initialData);
|
|
// To make sure that we are returning the same object reference to avoid
|
|
// unnecessary re-renders, we keep the previous snapshot and use deep
|
|
// comparison to check if we need to return a new one.
|
|
let memorizedSnapshot = clientSnapshot;
|
|
return [
|
|
()=>{
|
|
const newSnapshot = getSelectedCache(getCache());
|
|
const compareResult = isEqual(newSnapshot, memorizedSnapshot);
|
|
if (compareResult) {
|
|
// Mentally, we should always return the `memorizedSnapshot` here
|
|
// as there's no change between the new and old snapshots.
|
|
// However, since the `isEqual` function only compares selected fields,
|
|
// the values of the unselected fields might be changed. That's
|
|
// simply because we didn't track them.
|
|
// To support the case in https://github.com/vercel/swr/pull/2576,
|
|
// we need to update these fields in the `memorizedSnapshot` too
|
|
// with direct mutations to ensure the snapshot is always up-to-date
|
|
// even for the unselected fields, but only trigger re-renders when
|
|
// the selected fields are changed.
|
|
memorizedSnapshot.data = newSnapshot.data;
|
|
memorizedSnapshot.isLoading = newSnapshot.isLoading;
|
|
memorizedSnapshot.isValidating = newSnapshot.isValidating;
|
|
memorizedSnapshot.error = newSnapshot.error;
|
|
return memorizedSnapshot;
|
|
} else {
|
|
memorizedSnapshot = newSnapshot;
|
|
return newSnapshot;
|
|
}
|
|
},
|
|
()=>serverSnapshot
|
|
];
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
cache,
|
|
key
|
|
]);
|
|
// Get the current state that SWR should return.
|
|
const cached = shim.useSyncExternalStore(React.useCallback((callback)=>subscribeCache(key, (current, prev)=>{
|
|
if (!isEqual(prev, current)) callback();
|
|
}), // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[
|
|
cache,
|
|
key
|
|
]), getSnapshot[0], getSnapshot[1]);
|
|
const hasRevalidator = EVENT_REVALIDATORS[key] && EVENT_REVALIDATORS[key].length > 0;
|
|
const cachedData = cached.data;
|
|
const data = index_js.isUndefined(cachedData) ? fallback && index_js.isPromiseLike(fallback) ? use(fallback) : fallback : cachedData;
|
|
const error = cached.error;
|
|
// Use a ref to store previously returned data. Use the initial data as its initial value.
|
|
const laggyDataRef = React.useRef(data);
|
|
const returnedData = keepPreviousData ? index_js.isUndefined(cachedData) ? index_js.isUndefined(laggyDataRef.current) ? data : laggyDataRef.current : cachedData : data;
|
|
const hasKeyButNoData = key && index_js.isUndefined(data);
|
|
const hydrationRef = React.useRef(null);
|
|
// Note: the conditionally hook call is fine because the environment
|
|
// `IS_SERVER` never changes.
|
|
// @ts-expect-error -- use hydrationRef directly
|
|
!index_js.IS_SERVER && // getServerSnapshot is only called during hydration
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
shim.useSyncExternalStore(sub, ()=>{
|
|
hydrationRef.current = false;
|
|
return hydrationRef;
|
|
}, ()=>{
|
|
hydrationRef.current = true;
|
|
return hydrationRef;
|
|
});
|
|
const isHydration = hydrationRef.current;
|
|
// During the initial SSR render, warn if the key has no data pre-fetched via:
|
|
// - fallback data
|
|
// - preload calls
|
|
// - initial data from the cache provider
|
|
// We only warn once for each key during Hydration.
|
|
if (strictServerPrefetchWarning && isHydration && !suspense && hasKeyButNoData) {
|
|
console.warn(`Missing pre-initiated data for serialized key "${key}" during server-side rendering. Data fetching should be initiated on the server and provided to SWR via fallback data. You can set "strictServerPrefetchWarning: false" to disable this warning.`);
|
|
}
|
|
// Resolve the default validating state:
|
|
// If it's able to validate, and it should revalidate when mount, this will be true.
|
|
// - Suspense mode and there's stale data for the initial render.
|
|
// - Not suspense mode and there is no fallback data and `revalidateIfStale` is enabled.
|
|
// - `revalidateIfStale` is enabled but `data` is not defined.
|
|
const shouldDoInitialRevalidation = (()=>{
|
|
if (!key || !fetcher) return false;
|
|
// If it's paused, we skip revalidation.
|
|
if (getConfig().isPaused()) return false;
|
|
// if a key already has revalidators and also has error, we should not trigger revalidation
|
|
if (hasRevalidator && !index_js.isUndefined(error)) return false;
|
|
// If `revalidateOnMount` is set, we take the value directly.
|
|
if (isInitialMount && !index_js.isUndefined(revalidateOnMount)) return revalidateOnMount;
|
|
// Under suspense mode, it will always fetch on render if there is no
|
|
// stale data so no need to revalidate immediately mount it again.
|
|
// If data exists, only revalidate if `revalidateIfStale` is true.
|
|
if (suspense) return index_js.isUndefined(data) ? false : revalidateIfStale;
|
|
// If there is no stale data, we need to revalidate when mount;
|
|
// If `revalidateIfStale` is set to true, we will always revalidate.
|
|
return index_js.isUndefined(data) || revalidateIfStale;
|
|
})();
|
|
const defaultValidatingState = isInitialMount && shouldDoInitialRevalidation;
|
|
const isValidating = index_js.isUndefined(cached.isValidating) ? defaultValidatingState : cached.isValidating;
|
|
const isLoading = index_js.isUndefined(cached.isLoading) ? defaultValidatingState : cached.isLoading;
|
|
// The revalidation function is a carefully crafted wrapper of the original
|
|
// `fetcher`, to correctly handle the many edge cases.
|
|
const revalidate = React.useCallback(async (revalidateOpts)=>{
|
|
const currentFetcher = fetcherRef.current;
|
|
if (!key || !currentFetcher || unmountedRef.current || getConfig().isPaused()) {
|
|
return false;
|
|
}
|
|
let newData;
|
|
let startAt;
|
|
let loading = true;
|
|
const opts = revalidateOpts || {};
|
|
// If there is no ongoing concurrent request, or `dedupe` is not set, a
|
|
// new request should be initiated.
|
|
const shouldStartNewRequest = !FETCH[key] || !opts.dedupe;
|
|
/*
|
|
For React 17
|
|
Do unmount check for calls:
|
|
If key has changed during the revalidation, or the component has been
|
|
unmounted, old dispatch and old event callbacks should not take any
|
|
effect
|
|
|
|
For React 18
|
|
only check if key has changed
|
|
https://github.com/reactwg/react-18/discussions/82
|
|
*/ const callbackSafeguard = ()=>{
|
|
if (index_js.IS_REACT_LEGACY) {
|
|
return !unmountedRef.current && key === keyRef.current && initialMountedRef.current;
|
|
}
|
|
return key === keyRef.current;
|
|
};
|
|
// The final state object when the request finishes.
|
|
const finalState = {
|
|
isValidating: false,
|
|
isLoading: false
|
|
};
|
|
const finishRequestAndUpdateState = ()=>{
|
|
setCache(finalState);
|
|
};
|
|
const cleanupState = ()=>{
|
|
// Check if it's still the same request before deleting it.
|
|
const requestInfo = FETCH[key];
|
|
if (requestInfo && requestInfo[1] === startAt) {
|
|
delete FETCH[key];
|
|
}
|
|
};
|
|
// Start fetching. Change the `isValidating` state, update the cache.
|
|
const initialState = {
|
|
isValidating: true
|
|
};
|
|
// It is in the `isLoading` state, if and only if there is no cached data.
|
|
// This bypasses fallback data and laggy data.
|
|
if (index_js.isUndefined(getCache().data)) {
|
|
initialState.isLoading = true;
|
|
}
|
|
try {
|
|
if (shouldStartNewRequest) {
|
|
setCache(initialState);
|
|
// If no cache is being rendered currently (it shows a blank page),
|
|
// we trigger the loading slow event.
|
|
if (config.loadingTimeout && index_js.isUndefined(getCache().data)) {
|
|
setTimeout(()=>{
|
|
if (loading && callbackSafeguard()) {
|
|
getConfig().onLoadingSlow(key, config);
|
|
}
|
|
}, config.loadingTimeout);
|
|
}
|
|
// Start the request and save the timestamp.
|
|
// Key must be truthy if entering here.
|
|
FETCH[key] = [
|
|
currentFetcher(fnArg),
|
|
index_js.getTimestamp()
|
|
];
|
|
}
|
|
// Wait until the ongoing request is done. Deduplication is also
|
|
// considered here.
|
|
;
|
|
[newData, startAt] = FETCH[key];
|
|
newData = await newData;
|
|
if (shouldStartNewRequest) {
|
|
// If the request isn't interrupted, clean it up after the
|
|
// deduplication interval.
|
|
setTimeout(cleanupState, config.dedupingInterval);
|
|
}
|
|
// If there're other ongoing request(s), started after the current one,
|
|
// we need to ignore the current one to avoid possible race conditions:
|
|
// req1------------------>res1 (current one)
|
|
// req2---------------->res2
|
|
// the request that fired later will always be kept.
|
|
// The timestamp maybe be `undefined` or a number
|
|
if (!FETCH[key] || FETCH[key][1] !== startAt) {
|
|
if (shouldStartNewRequest) {
|
|
if (callbackSafeguard()) {
|
|
getConfig().onDiscarded(key);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Clear error.
|
|
finalState.error = index_js.UNDEFINED;
|
|
// If there're other mutations(s), that overlapped with the current revalidation:
|
|
// case 1:
|
|
// req------------------>res
|
|
// mutate------>end
|
|
// case 2:
|
|
// req------------>res
|
|
// mutate------>end
|
|
// case 3:
|
|
// req------------------>res
|
|
// mutate-------...---------->
|
|
// we have to ignore the revalidation result (res) because it's no longer fresh.
|
|
// meanwhile, a new revalidation should be triggered when the mutation ends.
|
|
const mutationInfo = MUTATION[key];
|
|
if (!index_js.isUndefined(mutationInfo) && // case 1
|
|
(startAt <= mutationInfo[0] || // case 2
|
|
startAt <= mutationInfo[1] || // case 3
|
|
mutationInfo[1] === 0)) {
|
|
finishRequestAndUpdateState();
|
|
if (shouldStartNewRequest) {
|
|
if (callbackSafeguard()) {
|
|
getConfig().onDiscarded(key);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Deep compare with the latest state to avoid extra re-renders.
|
|
// For local state, compare and assign.
|
|
const cacheData = getCache().data;
|
|
// Since the compare fn could be custom fn
|
|
// cacheData might be different from newData even when compare fn returns True
|
|
finalState.data = compare(cacheData, newData) ? cacheData : newData;
|
|
// Trigger the successful callback if it's the original request.
|
|
if (shouldStartNewRequest) {
|
|
if (callbackSafeguard()) {
|
|
getConfig().onSuccess(newData, key, config);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
cleanupState();
|
|
const currentConfig = getConfig();
|
|
const { shouldRetryOnError } = currentConfig;
|
|
// Not paused, we continue handling the error. Otherwise, discard it.
|
|
if (!currentConfig.isPaused()) {
|
|
// Get a new error, don't use deep comparison for errors.
|
|
finalState.error = err;
|
|
// Error event and retry logic. Only for the actual request, not
|
|
// deduped ones.
|
|
if (shouldStartNewRequest && callbackSafeguard()) {
|
|
currentConfig.onError(err, key, currentConfig);
|
|
if (shouldRetryOnError === true || index_js.isFunction(shouldRetryOnError) && shouldRetryOnError(err)) {
|
|
if (!getConfig().revalidateOnFocus || !getConfig().revalidateOnReconnect || isActive()) {
|
|
// If it's inactive, stop. It will auto-revalidate when
|
|
// refocusing or reconnecting.
|
|
// When retrying, deduplication is always enabled.
|
|
currentConfig.onErrorRetry(err, key, currentConfig, (_opts)=>{
|
|
const revalidators = EVENT_REVALIDATORS[key];
|
|
if (revalidators && revalidators[0]) {
|
|
revalidators[0](index_js.revalidateEvents.ERROR_REVALIDATE_EVENT, _opts);
|
|
}
|
|
}, {
|
|
retryCount: (opts.retryCount || 0) + 1,
|
|
dedupe: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Mark loading as stopped.
|
|
loading = false;
|
|
// Update the current hook's state.
|
|
finishRequestAndUpdateState();
|
|
return true;
|
|
}, // `setState` is immutable, and `eventsCallback`, `fnArg`, and
|
|
// `keyValidating` are depending on `key`, so we can exclude them from
|
|
// the deps array.
|
|
//
|
|
// FIXME:
|
|
// `fn` and `config` might be changed during the lifecycle,
|
|
// but they might be changed every render like this.
|
|
// `useSWR('key', () => fetch('/api/'), { suspense: true })`
|
|
// So we omit the values from the deps array
|
|
// even though it might cause unexpected behaviors.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[
|
|
key,
|
|
cache
|
|
]);
|
|
// Similar to the global mutate but bound to the current cache and key.
|
|
// `cache` isn't allowed to change during the lifecycle.
|
|
const boundMutate = React.useCallback(// Use callback to make sure `keyRef.current` returns latest result every time
|
|
(...args)=>{
|
|
return index_js.internalMutate(cache, keyRef.current, ...args);
|
|
}, // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[]);
|
|
// The logic for updating refs.
|
|
index_js.useIsomorphicLayoutEffect(()=>{
|
|
fetcherRef.current = fetcher;
|
|
configRef.current = config;
|
|
// Handle laggy data updates. If there's cached data of the current key,
|
|
// it'll be the correct reference.
|
|
if (!index_js.isUndefined(cachedData)) {
|
|
laggyDataRef.current = cachedData;
|
|
}
|
|
});
|
|
// After mounted or key changed.
|
|
index_js.useIsomorphicLayoutEffect(()=>{
|
|
if (!key) return;
|
|
const softRevalidate = revalidate.bind(index_js.UNDEFINED, WITH_DEDUPE);
|
|
let nextFocusRevalidatedAt = 0;
|
|
if (getConfig().revalidateOnFocus) {
|
|
const initNow = Date.now();
|
|
nextFocusRevalidatedAt = initNow + getConfig().focusThrottleInterval;
|
|
}
|
|
// Expose revalidators to global event listeners. So we can trigger
|
|
// revalidation from the outside.
|
|
const onRevalidate = (type, opts = {})=>{
|
|
if (type == index_js.revalidateEvents.FOCUS_EVENT) {
|
|
const now = Date.now();
|
|
if (getConfig().revalidateOnFocus && now > nextFocusRevalidatedAt && isActive()) {
|
|
nextFocusRevalidatedAt = now + getConfig().focusThrottleInterval;
|
|
softRevalidate();
|
|
}
|
|
} else if (type == index_js.revalidateEvents.RECONNECT_EVENT) {
|
|
if (getConfig().revalidateOnReconnect && isActive()) {
|
|
softRevalidate();
|
|
}
|
|
} else if (type == index_js.revalidateEvents.MUTATE_EVENT) {
|
|
return revalidate();
|
|
} else if (type == index_js.revalidateEvents.ERROR_REVALIDATE_EVENT) {
|
|
return revalidate(opts);
|
|
}
|
|
return;
|
|
};
|
|
const unsubEvents = index_js.subscribeCallback(key, EVENT_REVALIDATORS, onRevalidate);
|
|
// Mark the component as mounted and update corresponding refs.
|
|
unmountedRef.current = false;
|
|
keyRef.current = key;
|
|
initialMountedRef.current = true;
|
|
// Keep the original key in the cache.
|
|
setCache({
|
|
_k: fnArg
|
|
});
|
|
// Trigger a revalidation
|
|
if (shouldDoInitialRevalidation) {
|
|
// Performance optimization: if a request is already in progress for this key,
|
|
// skip the revalidation to avoid redundant work
|
|
if (!FETCH[key]) {
|
|
if (index_js.isUndefined(data) || index_js.IS_SERVER) {
|
|
// Revalidate immediately.
|
|
softRevalidate();
|
|
} else {
|
|
// Delay the revalidate if we have data to return so we won't block
|
|
// rendering.
|
|
index_js.rAF(softRevalidate);
|
|
}
|
|
}
|
|
}
|
|
return ()=>{
|
|
// Mark it as unmounted.
|
|
unmountedRef.current = true;
|
|
unsubEvents();
|
|
};
|
|
}, [
|
|
key
|
|
]);
|
|
// Polling
|
|
index_js.useIsomorphicLayoutEffect(()=>{
|
|
let timer;
|
|
function next() {
|
|
// Use the passed interval
|
|
// ...or invoke the function with the updated data to get the interval
|
|
const interval = index_js.isFunction(refreshInterval) ? refreshInterval(getCache().data) : refreshInterval;
|
|
// We only start the next interval if `refreshInterval` is not 0, and:
|
|
// - `force` is true, which is the start of polling
|
|
// - or `timer` is not 0, which means the effect wasn't canceled
|
|
if (interval && timer !== -1) {
|
|
timer = setTimeout(execute, interval);
|
|
}
|
|
}
|
|
function execute() {
|
|
// Check if it's OK to execute:
|
|
// Only revalidate when the page is visible, online, and not errored.
|
|
if (!getCache().error && (refreshWhenHidden || getConfig().isVisible()) && (refreshWhenOffline || getConfig().isOnline())) {
|
|
revalidate(WITH_DEDUPE).then(next);
|
|
} else {
|
|
// Schedule the next interval to check again.
|
|
next();
|
|
}
|
|
}
|
|
next();
|
|
return ()=>{
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = -1;
|
|
}
|
|
};
|
|
}, [
|
|
refreshInterval,
|
|
refreshWhenHidden,
|
|
refreshWhenOffline,
|
|
key
|
|
]);
|
|
// Display debug info in React DevTools.
|
|
React.useDebugValue(returnedData);
|
|
// In Suspense mode, we can't return the empty `data` state.
|
|
// If there is an `error`, the `error` needs to be thrown to the error boundary.
|
|
// If there is no `error`, the `revalidation` promise needs to be thrown to
|
|
// the suspense boundary.
|
|
if (suspense) {
|
|
// SWR should throw when trying to use Suspense on the server with React 18,
|
|
// without providing any fallback data. This causes hydration errors. See:
|
|
// https://github.com/vercel/swr/issues/1832
|
|
if (!index_js.IS_REACT_LEGACY && index_js.IS_SERVER && hasKeyButNoData) {
|
|
throw new Error('Fallback data is required when using Suspense in SSR.');
|
|
}
|
|
// Always update fetcher and config refs even with the Suspense mode.
|
|
if (hasKeyButNoData) {
|
|
fetcherRef.current = fetcher;
|
|
configRef.current = config;
|
|
unmountedRef.current = false;
|
|
}
|
|
const req = PRELOAD[key];
|
|
const mutateReq = !index_js.isUndefined(req) && hasKeyButNoData ? boundMutate(req) : resolvedUndef;
|
|
use(mutateReq);
|
|
if (!index_js.isUndefined(error) && hasKeyButNoData) {
|
|
throw error;
|
|
}
|
|
const revalidation = hasKeyButNoData ? revalidate(WITH_DEDUPE) : resolvedUndef;
|
|
if (!index_js.isUndefined(returnedData) && hasKeyButNoData) {
|
|
// @ts-ignore modify react promise status
|
|
revalidation.status = 'fulfilled';
|
|
// @ts-ignore modify react promise value
|
|
revalidation.value = true;
|
|
}
|
|
use(revalidation);
|
|
}
|
|
const swrResponse = {
|
|
mutate: boundMutate,
|
|
get data () {
|
|
stateDependencies.data = true;
|
|
return returnedData;
|
|
},
|
|
get error () {
|
|
stateDependencies.error = true;
|
|
return error;
|
|
},
|
|
get isValidating () {
|
|
stateDependencies.isValidating = true;
|
|
return isValidating;
|
|
},
|
|
get isLoading () {
|
|
stateDependencies.isLoading = true;
|
|
return isLoading;
|
|
}
|
|
};
|
|
return swrResponse;
|
|
};
|
|
const SWRConfig = index_js.OBJECT.defineProperty(index_js.SWRConfig, 'defaultValue', {
|
|
value: index_js.defaultConfig
|
|
});
|
|
/**
|
|
* A hook to fetch data.
|
|
*
|
|
* @see {@link https://swr.vercel.app}
|
|
*
|
|
* @example
|
|
* ```jsx
|
|
* import useSWR from 'swr'
|
|
* function Profile() {
|
|
* const { data, error, isLoading } = useSWR('/api/user', fetcher)
|
|
* if (error) return <div>failed to load</div>
|
|
* if (isLoading) return <div>loading...</div>
|
|
* return <div>hello {data.name}!</div>
|
|
* }
|
|
* ```
|
|
*/ const useSWR = index_js.withArgs(useSWRHandler);
|
|
|
|
// useSWR
|
|
|
|
Object.defineProperty(exports, "mutate", {
|
|
enumerable: true,
|
|
get: function () { return index_js.mutate; }
|
|
});
|
|
Object.defineProperty(exports, "preload", {
|
|
enumerable: true,
|
|
get: function () { return index_js.preload; }
|
|
});
|
|
Object.defineProperty(exports, "useSWRConfig", {
|
|
enumerable: true,
|
|
get: function () { return index_js.useSWRConfig; }
|
|
});
|
|
exports.SWRConfig = SWRConfig;
|
|
exports.default = useSWR;
|
|
exports.unstable_serialize = unstable_serialize;
|