Prevent useSWR from fetching until mutate() is called

Viewed 2399

I have a React application which uses SWR + Axios for data fetching (https://swr.vercel.app/docs/data-fetching). The issue is that my custom hook which uses useSwr is fetching all data initially whenever the hook is initialized. My goal is to fetch only when I call mutate. Currently the initial fetch is happening even without calling mutate. Any suggestion on how to achieve my goal here?

My application is wrapped in SWRConfig:

    <SWRConfig
      value={{
        fetcher,
      }}
    >
      <App/>
    </SWRConfig>

The fetcher is described as so:

const dataFetch = (url) => axios.get(url).then((res) => res.data);

function fetcher(...urls: string[]) {
  if (urls.length > 1) {
    return Promise.all(urls.map(dataFetch));
  }
  return dataFetch(urls);
}

My custom hook using useSwr

import useSWR, { useSWRConfig } from "swr";

export function useCars(registrationPlates: number[]): ICars {
  const { mutate } = useSWRConfig();
  const { data: carData} = useSWR<Car[]>(
    carsToFetchUrls(registrationPlates),  // returns string array with urls to fetch
    {
      revalidateOnFocus: false,    
      revalidateOnMount: false,
      revalidateOnReconnect: false,
      refreshWhenOffline: false,
      refreshWhenHidden: false,
      refreshInterval: 0,
    }
  );

  const getCar = (
    carRegistrationPlate: number,
  ): Car => {
    console.log(carData) // carData contains data from fetch even before calling mutate()

    void mutate();
    
    ...
}

Usage: (this will be located in some component that wants to use the useCars hook)

const { getCar } = useCars(carsRegistrationPlates);
2 Answers

You can use conditional fetching in the useSWR call to prevent it from making a request.

From useSWR Conditional Fetching docs:

Use null or pass a function as key to conditionally fetch data. If the function throws or returns a falsy value, SWR will not start the request.

export function useCars(registrationPlates: number[], shouldFetch): ICars {
    const { data: carData} = useSWR<Car[]>(
        shouldFetch ? carsToFetchUrls(registrationPlates) : null,
        { // Options here }
    );
    // ...
    return { carData, /**/ }
}

You can then use it as follows to avoid making the initial request.

const [shouldFetch, setShouldFetch] = useState(false);
const { carData } = useCars(carsRegistrationPlates, shouldFetch);

Then, when you want the make the request simply set shouldFetch to true.

setShouldFetch(true)

Here's a possible way of implementing what you are hoping to achieve. I've used a similar approach in one of my production app. Start by creating a custom swr hook as so

const useCars = (registrationPlates: number[]) => {
  const fetcher = (_: string) => {
    console.log("swr-key=", _);
    return dataFetch(registrationPlates);
  };

  const { data, error, isValidating, revalidate, mutate } = useSWR(`api/car/registration/${JSON.stringify(registrationPlates)}`, fetcher, {
    revalidateOnFocus: false,
  });

  return {
    data,
    error,
    isLoading: !data && !error,
    isValidating,
    revalidate,
    mutate,
  };
};

export { useCars };

Now, you can call this hook from any other component as

const { data, error, isLoading, isValidating, revalidate, mutate } = useCars(carsRegistrationPlates);

You now control what you want returned by what you pass to useCars above. Notice what is passed to the first argument to useSwr in our custom swr hook, this is the key swr uses to cache values and if this remains unchanged then swr will transparently returned the cached value.

Also, with this custom hook you are getting states such as loading, error etc. so you can take appropriate action for each of these states in your consuming component.

Related