353 lines
14 KiB
JavaScript
353 lines
14 KiB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
var react = require('react');
|
|
var useSWR = require('../index/index.js');
|
|
var index_js = require('../_internal/index.js');
|
|
var shim = require('use-sync-external-store/shim');
|
|
var constants_js = require('../_internal/constants.js');
|
|
|
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
|
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
|
|
|
|
// 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 getFirstPageKey = (getKey)=>{
|
|
return serialize(getKey ? getKey(0, null) : null)[0];
|
|
};
|
|
const unstable_serialize = (getKey)=>{
|
|
return constants_js.INFINITE_PREFIX + getFirstPageKey(getKey);
|
|
};
|
|
|
|
// We have to several type castings here because `useSWRInfinite` is a special
|
|
// hook where `key` and return type are not like the normal `useSWR` types.
|
|
const EMPTY_PROMISE = Promise.resolve();
|
|
const infinite = (useSWRNext)=>(getKey, fn, config)=>{
|
|
const didMountRef = react.useRef(false);
|
|
const { cache, initialSize = 1, revalidateAll = false, persistSize = false, revalidateFirstPage = true, revalidateOnMount = false, parallel = false } = config;
|
|
const [, , , PRELOAD] = index_js.SWRGlobalState.get(index_js.cache);
|
|
// The serialized key of the first page. This key will be used to store
|
|
// metadata of this SWR infinite hook.
|
|
let infiniteKey;
|
|
try {
|
|
infiniteKey = getFirstPageKey(getKey);
|
|
if (infiniteKey) infiniteKey = index_js.INFINITE_PREFIX + infiniteKey;
|
|
} catch (err) {
|
|
// Not ready yet.
|
|
}
|
|
const [get, set, subscribeCache] = index_js.createCacheHelper(cache, infiniteKey);
|
|
const getSnapshot = react.useCallback(()=>{
|
|
const size = index_js.isUndefined(get()._l) ? initialSize : get()._l;
|
|
return size;
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
cache,
|
|
infiniteKey,
|
|
initialSize
|
|
]);
|
|
shim.useSyncExternalStore(react.useCallback((callback)=>{
|
|
if (infiniteKey) return subscribeCache(infiniteKey, ()=>{
|
|
callback();
|
|
});
|
|
return ()=>{};
|
|
}, // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[
|
|
cache,
|
|
infiniteKey
|
|
]), getSnapshot, getSnapshot);
|
|
const resolvePageSize = react.useCallback(()=>{
|
|
const cachedPageSize = get()._l;
|
|
return index_js.isUndefined(cachedPageSize) ? initialSize : cachedPageSize;
|
|
// `cache` isn't allowed to change during the lifecycle
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
infiniteKey,
|
|
initialSize
|
|
]);
|
|
// keep the last page size to restore it with the persistSize option
|
|
const lastPageSizeRef = react.useRef(resolvePageSize());
|
|
// When the page key changes, we reset the page size if it's not persisted
|
|
index_js.useIsomorphicLayoutEffect(()=>{
|
|
if (!didMountRef.current) {
|
|
didMountRef.current = true;
|
|
return;
|
|
}
|
|
if (infiniteKey) {
|
|
// If the key has been changed, we keep the current page size if persistSize is enabled
|
|
// Otherwise, we reset the page size to cached pageSize
|
|
set({
|
|
_l: persistSize ? lastPageSizeRef.current : resolvePageSize()
|
|
});
|
|
}
|
|
// `initialSize` isn't allowed to change during the lifecycle
|
|
}, [
|
|
infiniteKey,
|
|
cache
|
|
]);
|
|
// Needs to check didMountRef during mounting, not in the fetcher
|
|
const shouldRevalidateOnMount = revalidateOnMount && !didMountRef.current;
|
|
// Actual SWR hook to load all pages in one fetcher.
|
|
const swr = useSWRNext(infiniteKey, async (key)=>{
|
|
// get the revalidate context
|
|
const forceRevalidateAll = get()._i;
|
|
const shouldRevalidatePage = get()._r;
|
|
set({
|
|
_r: index_js.UNDEFINED
|
|
});
|
|
// return an array of page data
|
|
const data = [];
|
|
const pageSize = resolvePageSize();
|
|
const [getCache] = index_js.createCacheHelper(cache, key);
|
|
const cacheData = getCache().data;
|
|
const revalidators = [];
|
|
let previousPageData = null;
|
|
for(let i = 0; i < pageSize; ++i){
|
|
const [pageKey, pageArg] = index_js.serialize(getKey(i, parallel ? null : previousPageData));
|
|
if (!pageKey) {
|
|
break;
|
|
}
|
|
const [getSWRCache, setSWRCache] = index_js.createCacheHelper(cache, pageKey);
|
|
// Get the cached page data.
|
|
let pageData = getSWRCache().data;
|
|
// should fetch (or revalidate) if:
|
|
// - `revalidateAll` is enabled
|
|
// - `mutate()` called
|
|
// - the cache is missing
|
|
// - it's the first page and it's not the initial render
|
|
// - `revalidateOnMount` is enabled and it's on mount
|
|
// - cache for that page has changed
|
|
const shouldFetchPage = revalidateAll || forceRevalidateAll || index_js.isUndefined(pageData) || revalidateFirstPage && !i && !index_js.isUndefined(cacheData) || shouldRevalidateOnMount || cacheData && !index_js.isUndefined(cacheData[i]) && !config.compare(cacheData[i], pageData);
|
|
if (fn && (typeof shouldRevalidatePage === 'function' ? shouldRevalidatePage(pageData, pageArg) : shouldFetchPage)) {
|
|
const revalidate = async ()=>{
|
|
const hasPreloadedRequest = pageKey in PRELOAD;
|
|
if (!hasPreloadedRequest) {
|
|
pageData = await fn(pageArg);
|
|
} else {
|
|
const req = PRELOAD[pageKey];
|
|
// delete the preload cache key before resolving it
|
|
// in case there's an error
|
|
delete PRELOAD[pageKey];
|
|
// get the page data from the preload cache
|
|
pageData = await req;
|
|
}
|
|
setSWRCache({
|
|
data: pageData,
|
|
_k: pageArg
|
|
});
|
|
data[i] = pageData;
|
|
};
|
|
if (parallel) {
|
|
revalidators.push(revalidate);
|
|
} else {
|
|
await revalidate();
|
|
}
|
|
} else {
|
|
data[i] = pageData;
|
|
}
|
|
if (!parallel) {
|
|
previousPageData = pageData;
|
|
}
|
|
}
|
|
// flush all revalidateions in parallel
|
|
if (parallel) {
|
|
await Promise.all(revalidators.map((r)=>r()));
|
|
}
|
|
// once we executed the data fetching based on the context, clear the context
|
|
set({
|
|
_i: index_js.UNDEFINED
|
|
});
|
|
// return the data
|
|
return data;
|
|
}, config);
|
|
const mutate = react.useCallback(// eslint-disable-next-line func-names
|
|
function(data, opts) {
|
|
// When passing as a boolean, it's explicitly used to disable/enable
|
|
// revalidation.
|
|
const options = typeof opts === 'boolean' ? {
|
|
revalidate: opts
|
|
} : opts || {};
|
|
// Default to true.
|
|
const shouldRevalidate = options.revalidate !== false;
|
|
// It is possible that the key is still falsy.
|
|
if (!infiniteKey) return EMPTY_PROMISE;
|
|
if (shouldRevalidate) {
|
|
if (!index_js.isUndefined(data)) {
|
|
// We only revalidate the pages that are changed
|
|
set({
|
|
_i: false,
|
|
_r: options.revalidate
|
|
});
|
|
} else {
|
|
// Calling `mutate()`, we revalidate all pages
|
|
set({
|
|
_i: true,
|
|
_r: options.revalidate
|
|
});
|
|
}
|
|
}
|
|
return arguments.length ? swr.mutate(data, {
|
|
...options,
|
|
revalidate: shouldRevalidate
|
|
}) : swr.mutate();
|
|
}, // swr.mutate is always the same reference
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[
|
|
infiniteKey,
|
|
cache
|
|
]);
|
|
// Extend the SWR API
|
|
const setSize = react.useCallback((arg)=>{
|
|
// It is possible that the key is still falsy.
|
|
if (!infiniteKey) return EMPTY_PROMISE;
|
|
const [, changeSize] = index_js.createCacheHelper(cache, infiniteKey);
|
|
let size;
|
|
if (index_js.isFunction(arg)) {
|
|
size = arg(resolvePageSize());
|
|
} else if (typeof arg == 'number') {
|
|
size = arg;
|
|
}
|
|
if (typeof size != 'number') return EMPTY_PROMISE;
|
|
changeSize({
|
|
_l: size
|
|
});
|
|
lastPageSizeRef.current = size;
|
|
// Calculate the page data after the size change.
|
|
const data = [];
|
|
const [getInfiniteCache] = index_js.createCacheHelper(cache, infiniteKey);
|
|
let previousPageData = null;
|
|
for(let i = 0; i < size; ++i){
|
|
const [pageKey] = index_js.serialize(getKey(i, previousPageData));
|
|
const [getCache] = index_js.createCacheHelper(cache, pageKey);
|
|
// Get the cached page data.
|
|
const pageData = pageKey ? getCache().data : index_js.UNDEFINED;
|
|
// Call `mutate` with infinte cache data if we can't get it from the page cache.
|
|
if (index_js.isUndefined(pageData)) {
|
|
return mutate(getInfiniteCache().data);
|
|
}
|
|
data.push(pageData);
|
|
previousPageData = pageData;
|
|
}
|
|
return mutate(data);
|
|
}, // exclude getKey from the dependencies, which isn't allowed to change during the lifecycle
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[
|
|
infiniteKey,
|
|
cache,
|
|
mutate,
|
|
resolvePageSize
|
|
]);
|
|
// Use getter functions to avoid unnecessary re-renders caused by triggering
|
|
// all the getters of the returned swr object.
|
|
return {
|
|
size: resolvePageSize(),
|
|
setSize,
|
|
mutate,
|
|
get data () {
|
|
return swr.data;
|
|
},
|
|
get error () {
|
|
return swr.error;
|
|
},
|
|
get isValidating () {
|
|
return swr.isValidating;
|
|
},
|
|
get isLoading () {
|
|
return swr.isLoading;
|
|
}
|
|
};
|
|
};
|
|
const useSWRInfinite = index_js.withMiddleware(useSWR__default.default, infinite);
|
|
|
|
exports.default = useSWRInfinite;
|
|
exports.infinite = infinite;
|
|
exports.unstable_serialize = unstable_serialize;
|