I simplified the reproduction
import useSWR from "swr";
import React, { useState, useEffect } from "react";
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(() => resolve("foo"), ms);
});
export default function App() {
const [state, setState] = useState(false);
useEffect(() => {
let timeout = setTimeout(() => {
setState(true);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, []);
const joke = useSWR("joke", () => sleep(1000));
if (!state || !
joke.data) {
return <div>loading</div>;
}
return <div>{joke.data}</div>;
}-
In first render,
joke.datawill not be accessed. -
After 1 second, the cache will be updated to
{ data: "foo" } -
After 2 second, state will be true and
joke.datawill be accessed. However, changingstateDepswill not trigger another render, sojoke.datawill still be undefined. -
when a new validation is triggered, after
sleepresolves, thejoke.datawill still beundefinedbecause cache has been update.
const cached = useSyncExternalStore(
useCallback(
(callback: () => void) =>
subscribeCache(
key,
(current: State<Data, any>, prev: State<Data, any>) => {
// prev { data: 'foo' } current {data: 'foo'}
if (!isEqual(prev, current)) callback();
}
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[cache, key]
),
getSnapshot[0],
getSnapshot[1]
);callback will not be executed, then getSnapshot[0] will not have chance to update the joke.data
- A quick but not complete fix for this could be removing the
!isEqual(prev, current)in useSES sub function, thenjoke.datacould be update in next validation. However it won't work foruseSWRImmutableand it has to wait for another revalidation. - I think a complete fix require a mechanism to notify
useSESimmediately thatstateDepshas changed, souseSESwould calculate a new snapshot. But this kind of mechanism may hurt performace of general SWR usage.
Previously i said
IMO it'more like a limitation.
Because in my mental model of SWR's dependcies collection. The following code
const { data, error, isLoading } = useSWR('/api', fetcher)are like
const swr = useSWR('/api', fetcher)
const data = useSubSWR(swr, 'data')
const error = useSubSWR(swr, 'error')
const isLoading = useSubSWR(swr, 'isLoading')which means you should not do
const swr = useSWR('/api', fetcher)
let data
if(xxx) {
data = useSubSWR(swr, 'data')
}Trying to access swr.data conditionaly breaks the rule of hooks.
Currently, i am not sure what to do next, would love to hear your ideas about this problem.