How to revalidate on fetcher change (like on key change)
memen45 opened this issue · comments
useSWRV works perfectly with reactive properties in the key:
const condition = ref<boolean>(false);
const key = ref<string>('someurl');
const {data, isValidating} = useSWRV<Response>(
() => {
if (condition) return key;
return undefined;
});
However, I would like to include a Authorization token in the header. Currently, I implemented a custom fetcher function to inject the header for each request (and also to handle 401 responses):
const fetcher = async (input: RequestInfo | URL, init?: RequestInit | undefined) => {
// before each request, add the updated accesstoken
if (init === undefined) {
init = {} as RequestInit;
}
const headers = new Headers(init.headers);
const token = useAccessTokenStore().getToken;
if (token !== '') headers.set("Authorization", 'Bearer ' + useAccessTokenStore().getToken);
init.headers = headers;
// console.log({ input, init });
return fetch(input, init);
}
const {data, isValidating} = useSWRV<Response>(
() => {
if (condition) return key;
return undefined;
},
fetcher
);
When the token is updated, revalidate is not triggered. Is there a (default) way to use such a 'global' token for all requests?
TLDR:
- headers cannot be part of the key (right?)
- headers should be added through custom fetcher function
- fetcher function itself is reactive, but does not trigger a revalidate
- is there a global revalidate trigger?
Headers are bound to your request client, e.g. an axios
instance or a fetch
request, etc.
If you want to add headers to all requests (if this is what you're asking) I would add them to a base request config and utilize that config in all of your fetcher functions.
is there a global revalidate trigger?
So you're wanting to trigger revalidate
whenever the value of token
is changed? Couldn't you just add the token
(or a hashed version of it) to the key
?
Alternatively you could make token a ref
and then add a watch
hook to fire revalidate
whenever token
changes.
Headers are bound to your request client, e.g. an
axios
instance or afetch
request, etc.If you want to add headers to all requests (if this is what you're asking) I would add them to a base request config and utilize that config in all of your fetcher functions.
You mean to e.g.
export fetcher
in a different file? Or is there something I am missing?
So you're wanting to trigger
revalidate
whenever the value oftoken
is changed? Couldn't you just add thetoken
(or a hashed version of it) to thekey
?
Yes! Exactly. I'm working in Vue and one component handles the user login and updates the token. Many other components use the token to obtain the data. Since the key is the url, I think adding something to it could result in a HTTP 400 - Bad Request.
Alternatively you could make token a
ref
and then add awatch
hook to firerevalidate
whenevertoken
changes.
That would require a watch hook in every component that uses useSWRV, but that is indeed a workaround!
I was hoping something like the reactivity of the key, e.g. being able to pass in a function instead of the fetcher function directly:
const {data, isValidating} = useSWRV<Response>(
() => {
if (condition) return key;
return undefined;
},
() => fetcher
);
Or similar through the config:
const {data, isValidating} = useSWRV<Response>(
() => {
if (condition) return key;
return undefined;
},
() => fetcher,
() => config
);
Let's pretend you utilize axios
for your requests. You can initialize and export a default axios instance to utilize within all of your custom fetchers:
// composables/useAxios.ts
import axios from 'axios'
import type { AxiosRequestConfig } from 'axios'
export default function useAxios(options: AxiosRequestConfig = {}) {
const axiosInstance = axios.create({
withCredentials: true,
// add headers, etc.
// ...
// or pass them in via the `options` object
...options,
})
return {
axiosInstance,
}
}
Then to use your instance:
const { axiosInstance } = useAxios()
Since the key is the url
Couldn't you change the key to not be the URL, or to utilize a key comprised of a concatenated string made up of the key + token
?
The key can be dynamic instead of just a static string, so you could do something like this:
// Let's assume you store your token in a Vue ref, accessing it via `token.value`
const token = ref<string>('')
const fetcher = async (input: RequestInfo | URL, init?: RequestInit | undefined) => {
// before each request, add the updated accesstoken
if (init === undefined) {
init = {} as RequestInit;
}
const headers = new Headers(init.headers);
token.value = useAccessTokenStore().getToken;
if (token.value !== '') headers.set("Authorization", 'Bearer ' + token.value);
init.headers = headers;
return fetch(input, init);
}
const { data } = useSWRV<Response>(
() => token.value && key && `${key}-${token.value}`, // dynamic based on key and token
fetcher,
})
Edit: This example ^ would have to be modified a bit so that the token
value is retrieved outside of the fetcher
function so that it has a value when useSWRV
is called.
Couldn't you change the key to not be the URL, or to utilize a key comprised of a concatenated string made up of the
key + token
?
How would the fetcher know what to fetch if the key is not the url?
const { data } = useSWRV<Response>( () => token.value && key && `${key}-${token.value}`, // dynamic based on key and token fetcher, })
Just tested a variation of this, and it indeed works, but requires importing the token in every file that uses useSWRV:
const {getToken} = storeToRefs(useAccessTokenStore());
const { data, error, isValidating, mutate } = useSWRV<Response>(
() => (getToken.value && (getToken.value !== '')) ? path : undefined,
fetcher,
default_options()
);
Just tested a variation of this, and it indeed works, but requires importing the token in every file that uses useSWRV
You could wrap useSWRV
in your own composable that extracts this logic into one place, and then call that composable instead of useSWRV
directly.
Example (again, ignore the pieces here you don't need from axios)
import useSWRV, { IConfig } from 'swrv'
import { AxiosResponse, AxiosError } from 'axios'
import { IKey, fetcherFn } from 'swrv/dist/types'
import { computed } from 'vue'
export default function useSwrvRequest<Data = unknown, Error = { message: string }>(key: IKey, fn?: fetcherFn<AxiosResponse<Data>>, config?: IConfig) {
const { getToken } = storeToRefs(useAccessTokenStore());
const { data, error, isValidating, mutate } = useSWRV<
AxiosResponse<Data>,
AxiosError<Error>
>(
() => !!getToken.value && key ? key : undefined,
fn,
...config
})
return {
data,
error,
isValidating,
mutate,
}
}
Thanks for your example! Very helpful and could be close to a (very nice!) solution. Only issue I am unable to solve is the error on trying to pass the key wrapped in a function again:
export function useSwrvRequest<Data = any, Error = any>(key: IKey, fn?: fetcherFn<Data>, config?: IConfig) {
const { getToken } = storeToRefs(useAccessTokenStore());
const { data, error, isValidating, mutate } = useSWRV<Data, Error>(
() => key, // << this gives an error, as we now create a () => () => firstkey type of function.
fn,
config
);
return {
data,
error,
isValidating,
mutate,
}
}
Think I got it! Thank you for thinking with me and proposing such a neat solution!
For future reference, a working solution below:
import { useAccessTokenStore } from "@/stores/accesstoken";
import { storeToRefs } from "pinia";
import useSWRV, { type IConfig } from "swrv";
import type { IKey, fetcherFn } from "swrv/dist/types";
import { unref } from "vue";
export function useSWRVRequest<Data = any, Error = any>(key: IKey, fn?: fetcherFn<Data>, config?: IConfig) {
const { getToken } = storeToRefs(useAccessTokenStore());
const { data, error, isValidating, mutate } = useSWRV<Data, Error>(
() => {
if (!getToken.value) return undefined;
return (typeof key === 'function') ? key() : unref(key);
},
fn,
config
);
return { data, error, isValidating, mutate, }
}