Is there an easy way, or if one could be added, to have useSWR keep the previous result while revalidating?
My case is a search field, and after I've gotten my first data, I'd really like the returned data to stick until a new request has completed (or failed). Currently the data goes back to undefined whenever the key changes.
That means it's not stale xD
馃 should we call this lib uwr :P
@quietshu sorry for the joke, I just couldn't resist...
I made a reproduction sandbox example, you can check it here https://codesandbox.io/s/swr-issue-192-qq880
I hope this will get fixed soon.
Thx!
It should probably be an option though, because in other cases it's probably a good thing that it does go through undefined. 馃
I suppose making it an option would also mean it could be added without breaking any existing usage. 馃憤
I'm not sure... data should always be stale.
If you need it to be undefined you can do this:
const weirdData = isValidating ? undefined : data;
Currently the data goes back to undefined whenever the key changes.
This is intended. The reason of this behavior is that although SWR returns stale data, it can't return wrong data. The data value should always be the result of that exact key.
But I agree here from the real world use cases, it can be an option to disable the strict key-data consistency.
What do you mean by wrong data?
@isBatak If you're starting out a loaded const { data } = useSWR('foo', fetcher), and then switch the key to 'bar', then data, if it stuck around, would be the wrong data because it belongs to 'foo', not 'bar'.
So that should be the default, but yeah, would be great with an option to switch how that works.
If you ask SWR to give you /api/users?id=1 and then you ask for /api/users?id=2 in the component (let's say the id was a state) returning the data of the id 1 it's the wrong data, because the user expects the data of the user 2 and that is not yet cached, if you go back to the id 1 it will return the previously cached data while it's revalidating and this is working since I used it a lot of times.
I think this will be solved once useTransition is stable, then you will be able to wrap the state change of the id in a startTransition and let React keep rendering the component with id 1 until the data of 2 arrives, or a lot of time has passed.
@sergiodxa What is this useTransition you speak of? Sounds interesting. 馃
It's a new React hook coming with Concurrent Mode: https://reactjs.org/docs/concurrent-mode-reference.html#usetransition
A component using it together with SWR would look like this:
const SUSPENSE_CONFIG = { timeoutMs: 1000 };
function User() {
const [id, setID] = React.useState(null);
const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
const { data } = useSWR(id ? `/api/users?id=${id}` : null, fetcher, { suspense: true });
function handleChange(event) {
const newID = event.target.value;
startTransition(() => {
setID(newID);
mutate(`/api/users?id=${newID}`, fetcher(`/api/users?id=${newID}`));
});
}
return (
<>
<input type="text" onChange={handleChange} value={id} />
{isPending && <Spinner />}
{data &&
<h1>{data.username}</h1>
}
</>
);
}
Something like that, without trying to run it so it may had typos, the idea here is to to tell React keep rendering what it's already on screen until the new UI triggered by setID and mutate it's ready or the value of timeoutMs has passed (a second in the example), meanwhile you get the isPending boolean to show to the user you are updating it but without clearing the old data.
if we pass array as key, for example ['foo', fetcherDep1, fetcherDep2], could we treat first element as a key and the rest as deps for revalidation?
I think it's better to just add a stickyResult: boolean option, rather than making that key/deps thing even more confusing 馃槢
For now you can use a custom hook for that:
function useStickyResult (value) {
const val = useRef()
if (value !== undefined) val.current = value
return val.current
}
Together with SWR:
const { data } = useSWR('/user?' + id)
const stickyData = useStickyResult(data)
Or combine them into a new hook:
function useStickySWR (...args) {
const swr = useSWR(...args)
const stickyData = useStickyResult(swr.data)
swr.data = stickyData
return swr
}
Adding an option stickyResult: boolean is a bit tricky because we need to consider errors too.
@quietshu is this custom hook still relevant?
I'm asking because of this merge https://github.com/zeit/swr/pull/186
swr.data is getter now, and isValidating is always false for some reason...
Ran into the same issue, and I don't think the above useStickySWR works, since as you mentioned @isBatak, you can't modify it.
Has any else worked around this? I want to differentiate _initial_ loading vs _reloading_ (with maybe slightly different params).
@stevewillard I changed useStickySWR a bit
import { useRef } from 'react';
import useSWR from 'swr';
export function useStickySWR(...args) {
const val = useRef();
const { data, isValidating, error, ...rest } = useSWR(...args);
if (data !== undefined) {
val.current = data;
}
return {
...rest,
isValidating,
error,
data: val.current,
};
}
But now it is deoptimized and will re-render component whether you use some pros or not.
But in my case, it is good enough solution.
How would this work in Suspense mode?
If I want the sticky hook to work with suspense would I just catch the promise from useSWR here instead of checking if data is undefined?
Most helpful comment
For now you can use a custom hook for that:
Together with SWR:
Or combine them into a new hook:
Adding an option
stickyResult: booleanis a bit tricky because we need to consider errors too.