useDownload
React hook to download a file.
Add the hook via the CLI:
sh
npx @novajslabs/cli add useDownload
sh
npx @novajslabs/cli add useDownload
sh
pnpm dlx @novajslabs/cli add useDownload
Or copy and paste the code into your project:
ts
import { useState } from "react";
export const useDownload = () => {
const [error, setError] = useState<Error | unknown | null>(null);
const [isDownloading, setIsDownloading] = useState<boolean>(false);
const [progress, setProgress] = useState<number | null>(null);
const handleResponse = async (response: Response): Promise<string> => {
if (!response.ok) {
throw new Error("Could not download file");
}
const contentLength = response.headers.get("content-length");
const reader = response.clone().body?.getReader();
if (!contentLength || !reader) {
const blob = await response.blob();
return createBlobURL(blob);
}
const stream = await getStream(contentLength, reader);
const newResponse = new Response(stream);
const blob = await newResponse.blob();
return createBlobURL(blob);
};
const getStream = async (
contentLength: string,
reader: ReadableStreamDefaultReader<Uint8Array>
): Promise<ReadableStream<Uint8Array>> => {
let loaded = 0;
const total = parseInt(contentLength, 10);
return new ReadableStream<Uint8Array>({
async start(controller) {
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.byteLength;
const percentage = Math.trunc((loaded / total) * 100);
setProgress(percentage);
controller.enqueue(value);
}
} catch (error) {
controller.error(error);
throw error;
} finally {
controller.close();
}
},
});
};
const createBlobURL = (blob: Blob): string => {
return window.URL.createObjectURL(blob);
};
const handleDownload = (fileName: string, url: string) => {
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
};
const downloadFile = async (fileName: string, fileUrl: string) => {
setIsDownloading(true);
setError(null);
setProgress(null);
try {
const response = await fetch(fileUrl);
const url = await handleResponse(response);
handleDownload(fileName, url);
} catch (error) {
setError(error);
} finally {
setIsDownloading(false);
}
};
return {
error,
isDownloading,
progress,
downloadFile,
};
};
js
import { useState } from "react";
export const useDownload = () => {
const [error, setError] = useState(null);
const [isDownloading, setIsDownloading] = useState(false);
const [progress, setProgress] = useState(null);
const handleResponse = async (response) => {
if (!response.ok) {
throw new Error("Could not download file");
}
const contentLength = response.headers.get("content-length");
const reader = response.clone().body?.getReader();
if (!contentLength || !reader) {
const blob = await response.blob();
return createBlobURL(blob);
}
const stream = await getStream(contentLength, reader);
const newResponse = new Response(stream);
const blob = await newResponse.blob();
return createBlobURL(blob);
};
const getStream = async (contentLength, reader) => {
let loaded = 0;
const total = parseInt(contentLength, 10);
return new ReadableStream({
async start(controller) {
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.byteLength;
const percentage = Math.trunc((loaded / total) * 100);
setProgress(percentage);
controller.enqueue(value);
}
} catch (error) {
controller.error(error);
throw error;
} finally {
controller.close();
}
},
});
};
const createBlobURL = (blob) => {
return window.URL.createObjectURL(blob);
};
const handleDownload = (fileName, url) => {
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
};
const downloadFile = async (fileName, fileUrl) => {
setIsDownloading(true);
setError(null);
setProgress(null);
try {
const response = await fetch(fileUrl);
const url = await handleResponse(response);
handleDownload(fileName, url);
} catch (error) {
setError(error);
} finally {
setIsDownloading(false);
}
};
return {
error,
isDownloading,
progress,
downloadFile,
};
};
Requirements
React 16.8 or higher
Return values
error
Type: Error | unknown | null
Represents any error that occurs during the download process, or null
if no error occurs.
isDownloading
Type: boolean
Indicates whether a file is currently being downloaded (true
) or not (false
).
downloadFile
Type: function
Initiate the file download process. Accepts two parameters: fileName
, a string that represents the name to be given to the downloaded file, and fileUrl
, a string that represents the URL from which the file is to be downloaded.
progress
Type: number | null
Indicates the percentage of download progress.
Example
tsx
import { useDownload } from "./hooks/useDownload";
const App = () => {
const { error, isDownloading, downloadFile } = useDownload();
const handleDownload = () => {
const fileName = "example.pdf";
const fileUrl = "https://example.com/example.pdf";
downloadFile(fileName, fileUrl);
};
return (
<div>
<button onClick={handleDownload} disabled={isDownloading}>
{isDownloading ? "Downloading..." : "Download File"}
</button>
{error && <div>Error: {error.message}</div>}
</div>
);
};
export default App;
Use cases
Here are some use cases where this React hook is useful:
- Downloading PDF invoices in an e-commerce application
- Exporting data as a CSV file in a data visualization dashboard
- Providing downloadable resources (e.g., whitepapers, guides) on a website