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.data
will not be accessed. -
After 1 second, the cache will be updated to
{ data: "foo" }
-
After 2 second, state will be true and
joke.data
will be accessed. However, changingstateDeps
will not trigger another render, sojoke.data
will still be undefined. -
when a new validation is triggered, after
sleep
resolves, thejoke.data
will still beundefined
because 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.data
could be update in next validation. However it won't work foruseSWRImmutable
and it has to wait for another revalidation. - I think a complete fix require a mechanism to notify
useSES
immediately thatstateDeps
has changed, souseSES
would 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.