React hook to download a file.
Add the hook via the CLI:
npx @novajslabs/cli add useDownload
Or copy and paste the code into your project:
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,
}
}
React 16.8 or higher
- Name
error
- Type
- Error | unknown | null
- Description
Represents any error that occurs during the download process, or null if no error occurs.
- Name
isDownloading
- Type
- boolean
- Description
Indicates whether a file is currently being downloaded (true) or not(false).
- Name
downloadFile
- Type
- function
- Description
Initiate the file download process. Accept two parameters:
fileName
a string that represents the name to be given to the downloaded file andfileUrl
a string that represents the URL from which the file is to be downloaded.
- Name
progress
- Type
- number | null
- Description
Indicates the percentage of download progress.
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
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