'How to reset my react hook useDownloadWithProgress
I created a custom React hook useDownloadWithProgress(). This hook will allow downloading a file in the client and provide a download progress indicator.
I adapted this from a code snippet I found that would use XMLHttpRequest to download a resource and once it is downloaded, it is inserted into the DOM as a blob on an anchor tag and finally saved to the computer by artificially clicking that anchor tag.
The hook is consumed like this:
const [progress, setUrl] = useDownloadWithProgress();
...
<button onClick={()=> setUrl("abc.jpg")}>Download</button>
Progress is a state variable expressed as a number between 0-100 and can be used to control for example a progress bar.
The hook itself currently looks like this:
import { useEffect, useRef, useState } from "react";
const useDownloadWithProgress = () => {
const [progress, setProgress] = useState<number>();
const [url, setUrl] = useState<string | URL>();
const reqRef = useRef(new XMLHttpRequest());
const clearState = () => {
console.log("Clearing state");
reqRef.current = new XMLHttpRequest();
setProgress(undefined);
setUrl(undefined);
};
useEffect(() => {
const request = reqRef.current;
request.responseType = "blob";
request.onprogress = function ({ loaded, total }) {
setProgress(Math.floor((loaded / total) * 100));
};
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
const imageURL = window.URL.createObjectURL(this.response);
const anchor = document.createElement("a");
anchor.href = imageURL;
const urlElements = imageURL.split("/");
anchor.download = urlElements[urlElements.length - 1];
document.body.appendChild(anchor);
anchor.click();
clearState();
}
};
return () => {
request.onprogress = null;
request.onreadystatechange = null;
};
}, []);
useEffect(() => {
if (!url) {
return;
}
const request = reqRef.current;
request.open("get", url, true);
request.send();
}, [url]);
return [progress, setUrl];
};
export default useDownloadWithProgress;
Questions:
My main problem now is I can't figure out how to reset the state of my hook after a file has been downloaded. After successfully downloading a file I would like to be able to click the button again and download the same file. Currently it does not work and in some situations I might get an error telling me that the responseType "blob" cannot be set when the request is in loading or completed state. You can see my current attempt at resetting the state in the function
clearState()but it does not seem to work.Does anyone have experience with an approach like this and larger file sizes? Perhaps I should provide a fallback for files over a certain size and link directly to the file and let the browser handle the download natively, as to not kill the browser by trying to insert a multiple gigabite blob into the DOM? I don't expect to have file size more than a couple of MB, but since it is a hook that should be used universally, it would probably be worth taking larger files into account.
Am I correctly removing eventlisteners for
onprogressandonreadystatechange?Is it "kosher" to manipulate the DOM like inside
onreadystatechangewhen working with React? Normally I would use JSX and click the element with a Ref, but since stole this bit from an example in vanilla JS and since I figured this is what React would do under the hood anyway, I figured it should be fine. But perhaps there is some side-effect of doing it like this?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
