// 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. exportconst stableHash = (arg: any): string => { const type = typeof arg const constructor = arg && arg.constructor const isDate = constructor == Date
letresult: any letindex: any
if (OBJECT(arg) === arg && !isDate && constructor != RegExp) { result = table.get(arg) if (result) return result result = ++counter + '~' table.set(arg, result)
if (constructor == Array) { result = '@' for (index = 0; index < arg.length; index++) { result += stableHash(arg[index]) + ',' } table.set(arg, result) } if (constructor == OBJECT) { result = '#' const keys = OBJECT.keys(arg).sort() while (!isUndefined((index = keys.pop() as string))) { 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 }
// Trigger a revalidation. if (shouldDoInitialRevalidation) { if (isUndefined(data) || IS_SERVER) { // Revalidate immediately. softRevalidate(); } else { // Delay the revalidate if we have data to return so we won't block // rendering. rAF(softRevalidate); } }
return() => {
}; }, [key]);
// The revalidation function is a carefully crafted wrapper of the original // `fetcher`, to correctly handle the many edge cases. const revalidate = useCallback( async (revalidateOpts?: RevalidatorOptions): Promise<boolean> => { // If there is no ongoing concurrent request, or `dedupe` is not set, a // new request should be initiated. const shouldStartNewRequest = !FETCH[key] || !opts.dedupe;
try { if (shouldStartNewRequest) { setCache(initialState);
// Start the request and save the timestamp. // Key must be truthy if entering here. FETCH[key] = [currentFetcher(_key), 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) { returnfalse; }
// Clear error. finalState.error = UNDEFINED;
// 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; } catch (err: any) { cleanupState(); }
// Update the current hook's state. setCache(finalState)
// React currently throws a warning when using useLayoutEffect on the server. // To get around it, we can conditionally useEffect on the server (no-op) and // useLayoutEffect in the browser. exportconst useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect