diff --git a/src/use-swr.ts b/src/use-swr.ts index f8e816da31..842e656fa3 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -8,7 +8,8 @@ import { UNDEFINED, OBJECT, mergeObjects, - isFunction + isFunction, + IS_REACT_LEGACY } from './utils/helper' import ConfigProvider from './utils/config-context' import { useStateWithDeps } from './utils/state' @@ -162,14 +163,27 @@ export const useSWRHandler = ( // new request should be initiated. const shouldStartNewRequest = !FETCH[key] || !opts.dedupe - // 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. - const isCurrentKeyMounted = () => - !unmountedRef.current && - key === keyRef.current && - initialMountedRef.current + /* + 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 (IS_REACT_LEGACY) { + return ( + !unmountedRef.current && + key === keyRef.current && + initialMountedRef.current + ) + } + return key === keyRef.current + } const cleanupState = () => { // Check if it's still the same request before deleting. @@ -184,7 +198,7 @@ export const useSWRHandler = ( const finishRequestAndUpdateState = () => { set({ isValidating: false }) // We can only set state if it's safe (still mounted with the same key). - if (isCurrentKeyMounted()) { + if (callbackSafeguard()) { setState(newState) } } @@ -205,7 +219,7 @@ export const useSWRHandler = ( // we trigger the loading slow event. if (config.loadingTimeout && isUndefined(get().data)) { setTimeout(() => { - if (loading && isCurrentKeyMounted()) { + if (loading && callbackSafeguard()) { getConfig().onLoadingSlow(key, config) } }, config.loadingTimeout) @@ -238,7 +252,7 @@ export const useSWRHandler = ( // The timestamp maybe be `undefined` or a number if (!FETCH[key] || FETCH[key][1] !== startAt) { if (shouldStartNewRequest) { - if (isCurrentKeyMounted()) { + if (callbackSafeguard()) { getConfig().onDiscarded(key) } } @@ -273,7 +287,7 @@ export const useSWRHandler = ( ) { finishRequestAndUpdateState() if (shouldStartNewRequest) { - if (isCurrentKeyMounted()) { + if (callbackSafeguard()) { getConfig().onDiscarded(key) } } @@ -300,7 +314,7 @@ export const useSWRHandler = ( // Trigger the successful callback if it's the original request. if (shouldStartNewRequest) { - if (isCurrentKeyMounted()) { + if (callbackSafeguard()) { getConfig().onSuccess(newData, key, config) } } @@ -318,7 +332,7 @@ export const useSWRHandler = ( // Error event and retry logic. Only for the actual request, not // deduped ones. - if (shouldStartNewRequest && isCurrentKeyMounted()) { + if (shouldStartNewRequest && callbackSafeguard()) { currentConfig.onError(err, key, currentConfig) if ( shouldRetryOnError === true || @@ -353,7 +367,7 @@ export const useSWRHandler = ( // Here is the source of the request, need to tell all other hooks to // update their states. - if (isCurrentKeyMounted() && shouldStartNewRequest) { + if (callbackSafeguard() && shouldStartNewRequest) { broadcastState(cache, key, { ...newState, isValidating: false }) } diff --git a/src/utils/broadcast-state.ts b/src/utils/broadcast-state.ts index 011d306e07..491cf0fec2 100644 --- a/src/utils/broadcast-state.ts +++ b/src/utils/broadcast-state.ts @@ -1,5 +1,5 @@ import { Broadcaster } from '../types' -import { SWRGlobalState, GlobalState } from './global-state' +import { SWRGlobalState } from './global-state' import * as revalidateEvents from '../constants' import { createCacheHelper } from './cache' @@ -10,33 +10,34 @@ export const broadcastState: Broadcaster = ( revalidate, broadcast = true ) => { - const [EVENT_REVALIDATORS, STATE_UPDATERS, , FETCH] = SWRGlobalState.get( - cache - ) as GlobalState - const revalidators = EVENT_REVALIDATORS[key] - const updaters = STATE_UPDATERS[key] + const stateResult = SWRGlobalState.get(cache) + if (stateResult) { + const [EVENT_REVALIDATORS, STATE_UPDATERS, , FETCH] = stateResult + const revalidators = EVENT_REVALIDATORS[key] + const updaters = STATE_UPDATERS[key] - const [get] = createCacheHelper(cache, key) + const [get] = createCacheHelper(cache, key) - // Cache was populated, update states of all hooks. - if (broadcast && updaters) { - for (let i = 0; i < updaters.length; ++i) { - updaters[i](state) + // Cache was populated, update states of all hooks. + if (broadcast && updaters) { + for (let i = 0; i < updaters.length; ++i) { + updaters[i](state) + } } - } - // If we also need to revalidate, only do it for the first hook. - if (revalidate) { - // Invalidate the key by deleting the concurrent request markers so new - // requests will not be deduped. - delete FETCH[key] + // If we also need to revalidate, only do it for the first hook. + if (revalidate) { + // Invalidate the key by deleting the concurrent request markers so new + // requests will not be deduped. + delete FETCH[key] - if (revalidators && revalidators[0]) { - return revalidators[0](revalidateEvents.MUTATE_EVENT).then( - () => get().data - ) + if (revalidators && revalidators[0]) { + return revalidators[0](revalidateEvents.MUTATE_EVENT).then( + () => get().data + ) + } } - } - return get().data + return get().data + } } diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 68eb108f1d..cd99e9d822 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -1,3 +1,4 @@ +import { version } from 'react' export const noop = () => {} // Using noop() as the undefined value as undefined can possibly be replaced @@ -5,7 +6,7 @@ export const noop = () => {} // to ensure that tsc doesn't remove the __NOINLINE__ comment. // prettier-ignore export const UNDEFINED = (/*#__NOINLINE__*/ noop()) as undefined - +export const IS_REACT_LEGACY = !version.startsWith('18') export const OBJECT = Object export const isUndefined = (v: any): v is undefined => v === UNDEFINED