-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keep previous result while revalidating #192
Comments
That means it's not @quietshu sorry for the joke, I just couldn't resist... |
It should probably be an option though, because in other cases it's probably a good thing that it does go through I suppose making it an option would also mean it could be added without breaking any existing usage. 👍 |
I'm not sure... |
This is intended. The reason of this behavior is that although SWR returns stale data, it can't return wrong data. The 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 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 I think this will be solved once |
@sergiodxa What is this |
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 |
if we pass array as key, for example |
I think it's better to just add a |
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 |
@quietshu is this custom hook still relevant? |
Ran into the same issue, and I don't think the above Has any else worked around this? I want to differentiate initial loading vs reloading (with maybe slightly different params). |
@stevewillard I changed
But now it is deoptimized and will re-render component whether you use some pros or not. |
How would this work in Suspense mode? |
Is there a good way to do this now? Can we keep wrong data rendered until we fetch right data, I.e. in pagination, it seems a very bad user experience to show the loading skeleton (the suspense fallback) between page transitions, I'd much rather display a loading spinner to indicate a transition is taking place. I'm new to |
@bobbyhadz I would consider the solution provided by @isBatak by resolving this through a ref 'good enough' in a sense that it works, but it would indeed be a great feature to have withint SWR itself. My current situation is as follows:
This all works ok, but it results in the following quirk that could be considered weird/buggy from a user perspective: While filtering, during revalidating, the user sees vacancies and filter options that were initially provided through initialData. Even when these items and filters might not even be relevant to the user anymore. In case of a mediocre performing API / a slow connection this just feels off/unacceptable. Here's a clip of the situation: Solution for my situation, inspired by comments above
|
We've made a handle middleware for this use case: Keep Previous Result. You can easily customize it based on your own use case. Please also check the SWR 1.0 blog post! 🎉 |
Reopening as I think this can be a good built-in feature. |
is the middle still the best way to handle this kind of situation? |
@shuding I have the same problem, in my case I use |
Facing the same issue as @dqunbp! When I'm using the I believe the issue lies in the following line:
In the case where |
@steven-tey This is my workaround to pass I also wrap it to my custom
Hook code. Click to expand!import { AxiosError as A, AxiosResponseTransformer as ART } from 'axios'
import { useRef } from 'react'
import useSWR, { SWRConfiguration } from 'swr'
import { SWRKey } from '../createSWRConfig'
import { SWRResult, SWRResultWithFallback } from '../types'
import createTransformMiddleware from './middlewares/transform'
import laggy from './middlewares/laggy'
type Config<D, E> = SWRConfiguration<D, E> & {
transform?: ART
}
type LaggyProps = { isLagging: boolean; resetLaggy(): void }
// Config without fallback & Response without fallback
// OF - omit fallback
// OFR - omit fallback response
type OF<D, E> = Omit<Config<D, E>, 'fallbackData'>
type OFR<D, E> = SWRResult<D, E> & LaggyProps
// Config with fallback & Response with fallback
// F - fallback
// FR - fallback response
type F<D, E> = OF<D, E> & { fallbackData: D }
type FR<D, E> = SWRResultWithFallback<D, E> & LaggyProps
// useQuery result depends on fallback type
// QC - query config
// QR - query response
type QC<D, E> = Config<D, E>
type QR<D, E> = D extends undefined ? OFR<D, E> : FR<D, E>
function useQuery<D, E = A>(key: SWRKey | null, options?: OF<D, E>): OFR<D, E>
function useQuery<D, E = A>(key: SWRKey | null, options?: F<D, E>): FR<D, E>
function useQuery<D, E = A>(key: SWRKey | null, options?: QC<D, E>): QR<D, E> {
const { url = '', params = {} } = key || {}
const { fallbackData, transform, use = [], ...restOptions } = options || {}
const fallback = useRef(fallbackData)
const transformMiddleware = createTransformMiddleware(transform)
const response = useSWR<D, E, SWRKey | null>(key ? { url, params } : null, {
...restOptions,
use: [transformMiddleware, laggy, ...use],
})
const { data, error, mutate, isValidating, isLagging, resetLaggy } =
response as QR<D, E>
return {
data:
typeof data === 'undefined' && typeof fallback.current !== 'undefined'
? fallback.current
: data,
error,
mutate,
isValidating,
isError: Boolean(error),
isLoading: !error && !data,
isLagging,
resetLaggy,
} as QR<D, E>
}
export default useQuery Types. Click to expand!type SWRResponseOmitData<Error> = Omit<SWRResponse<unknown, Error>, 'data'>
export type SWRResult<Data, Error> =
| ({
data: undefined
isLoading: true
isError: false
} & SWRResponseOmitData<Error>)
| ({
data: Data
isLoading: false
isError: boolean
} & SWRResponseOmitData<Error>)
export type SWRResultWithFallback<Data, Error = unknown> = {
data: Data
isLoading: boolean
isError: boolean
} & SWRResponseOmitData<Error> Default fetcher. Click to expand!import { AxiosRequestConfig, AxiosResponseTransformer } from 'axios'
import { APIData } from 'types/commonTypes'
import createAPIClient from './apiClient/create'
export type SWRKey = {
url: string
params?: AxiosRequestConfig['params']
}
const createSWRConfig = (apiData: APIData) => ({
fetcher: async (
{ url, params }: SWRKey,
transformResponse?: AxiosResponseTransformer
) => {
const client = createAPIClient(apiData)
const { data } = await client.get(url, {
transformResponse,
data: {}, // required to add Content-Type to request
params,
})
return data
},
})
export default createSWRConfig Transform middleware. Click to expand!import { Middleware } from 'swr'
import { AxiosResponseTransformer } from 'axios'
import { SWRKey } from 'requestSystem/createSWRConfig'
const createTransformMiddleware =
(transform?: AxiosResponseTransformer): Middleware =>
(next) =>
(k, fetcher, config) => {
const extendedFetcher = fetcher
? (kk: SWRKey) => fetcher(kk, transform)
: fetcher
return next(k, extendedFetcher, config)
}
export default createTransformMiddleware P.S. |
I don't think there are bugs within |
Aiming to add a built-in solution for this in the upcoming 2.0 version. |
My first step is to add #1925, which completes the necessary states to implement laggy and fallback. And then, we will look for a way to enable the laggy middleware via just one option |
Can confirm that adding
Note: this feature is in pre-release so you'll have to do @dqunbp can you try downloading the latest version, try it out and let me know if it works? :) |
Very useful when paginating tables ! |
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 returneddata
to stick until a new request has completed (or failed). Currently thedata
goes back toundefined
whenever the key changes.The text was updated successfully, but these errors were encountered: