Manny
Manny

Reputation: 156

How to keep refetching (refetch) `useQuery` even after the component unmounts?

I am using useQuery and its refetch option to download a file in the frontend. The download process is of two steps, and I am displaying the below notifications to the users:

  1. Download process is started, the file will automatically download once the file is ready
  2. The download has now been completed

Both these notifications are displayed to the user, and the idea is to allow the user to navigate anywhere in the application while the download is being processed.

There are two APIs:

  1. API 1 - used for displaying the first notification. I use refetch option with this to check if the download is ready.

  2. API 2 - used for downloading the file once the API returns "Ready for download" status

For this, I have created a custom hook where I call the useQuery for each API

Custom hook structure:

export const useFileDownload = () => {

 const [fileReady, setFileReady] = useState(false);

 const { isError, isLoading, mutate } = useDownloadBegin({ //this uses useQuery with refetch option
    onSuccess: (data) => {
      setFileReady(true);
    }
 });

 const { isError, isLoading, mutate } = useDownloadFile({ //this downloads file using useQuery
    enabled: fileReady;
    . . .
 });


}

My Component:

<DownloadModal {props} />

Download Modal:

export const DownloadModal = ({...props}) => {
   const { isDownloading } = useFileDownload();
   
   return(
    <>Download Modal</>
   )
  

}

What is the issue?

Once I navigate to another page, API 1 stops getting executed, and I am never able to download the file using API 2. It works absolutely fine if I stay on the same page.

I believe, the hook does not get executed once I go to another page, as the component gets unmounted.

How do I make sure that hook gets executed asynchronously even when navigated away from the component?

What have I tried?

I explored different options available in tanstack useQuery like refetchIntervalInBackground.

Also, since I do not have a store in my application, I am trying to work around context to see if I can use it for my purpose. But, I am still not sure about it.

Upvotes: 3

Views: 943

Answers (1)

Chuckatron
Chuckatron

Reputation: 306

You're on the right track. You need a Context Provider at the top of your app, such that every page the user might visit following the download process initiating will be within the provider's context, e.g.

<DownloadProvider>
  <Page1 />
  <Page2>
    <Page2a />
    <Page2b />
  </Page2>
  <Page3 />
</DownloadProvider>

You can make use of your existing useFileDownload hook in the provider. You don't say what initiates the download process, so I'm guessing it's a button click or similar, in which case your useFileDownload hook will need to return the ability for the hook to know it can begin the process, e.g. (using an enableDownload state property as demonstrated here):

Custom hook

export const useFileDownload = () => {

  const [enableDownload, setEnableDownload] = useState(false);    
  const [fileReady, setFileReady] = useState(false);

  const {
    isError,
    isLoading,
    mutate
  } = useDownloadBegin({ //this uses useQuery with refetch option
    enabled: enableDownload,
    onSuccess: (data) => {
      setFileReady(true);
    }
  });

  const {
    isError,
    isLoading,
    mutate
  } = useDownloadFile({ //this downloads file using useQuery
    enabled: fileReady;
    ...
  });

  return {
    setEnableDownload
  };
}

The idea being that whatever UI initiates the download process can just call setEnableDownload(true).

Your context provider will make use of this hook, this will make setEnableDownload available to any descendant component that needs to use it:

download-provider.jsx

const DownloadContext = createContext({});

const DownloadProvider = ({ children}) => {
  const { setEnableDownload }  = useFileDownload();

  const contextValues = { setEnableDownload };

  return (
    <DownloadContext.Provider value={contextValues}>
      {children}
    </DownloadContext.Provider>
  );
};

export default DownloadProvider;
export { DownloadContext };

You can then add your context provider to the top of your page hierarchy:

import DownloadProvider from './download-provider.jsx';

...

    return (
      <DownloadProvider>
        <...Top level page component...>
      </DownloadProvider>
    );

...

And, finally, in the component that needs to initiate the download, you can use the provided setEnableDownload method:

import { DownloadContext } from '../../download-provider.jsx';

...

  const { setEnableDownload }  = useContext(DownloadContext);

...

and call it as appropriate to initiate the download process, and because the hook is being used by a provider at the top of your component tree, it will continue operating regardless of the user navigating around the app.

Upvotes: 1

Related Questions