Kong / swrv

Stale-while-revalidate data fetching for Vue

Home Page:https://docs-swrv.netlify.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 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.

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 of token is changed? Couldn't you just add the token (or a hashed version of it) to the key?

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 a watch hook to fire revalidate whenever token 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, }
}