React
React + custom hook + typescript to download a file through API
Anubhav Goel
Clock Icon
7 min read

Abstract

Implementing file downloads in React applications becomes complex when dealing with authenticated APIs. While basic downloads can be handled with simple anchor tags, secure file downloads through API endpoints require careful handling of authentication, binary data, and user feedback.

This article presents a TypeScript-based custom hook solution that encapsulates these complexities into a reusable implementation, providing a clean API for components while handling the technical challenges of secure file downloads.

Background and Problem Statement
Implementing secure file downloads in React applications presents several technical challenges.
1
Authentication Handling
Files behind protected endpoints require proper token management and header handling.
2
Binary Data Management
Handling blob responses and converting them to downloadable files.
3
User Experience
Managing download progress, error states, and successful completion.

Solution Details

Let's break down the solution into two main approaches:

The simplest approach using anchor tag:

<a href="https://www.some/url/to/file" target="__blank">Download</a>

This works for public files but fails for authenticated resources:

<!-- Won't work for protected files -->
<a href="https://api.example.com/protected-file">Download</a>

2. API-Based Solution

Our solution involves:

  1. Calling authenticated API
  2. Handling blob response
  3. Creating temporary URL
  4. Triggering download programmatically

Implementation Details

1. Project Setup

First, create a React project with TypeScript and install dependencies:

npx create-react-app react-download-file-axios --template typescript
npm i axios @types/axios luxon @types/react bootstrap react-bootstrap

2. Component Structure

Our implementation consists of three main parts:

  1. Button Component (UI + Loading States)
  2. Custom Download Hook (Core Logic)
  3. Implementation Component (Usage Example)

Button Component

A reusable button handling loading states:

import React from "react";
import { Button as InternalButton, Spinner } from "react-bootstrap";

export enum ButtonState {
  Primary = "Primary",
  Loading = "Loading",
}

interface ButtonProps {
  readonly buttonState: ButtonState;
  readonly onClick: () => void;
  readonly label: string;
}

export const Button: React.FC<ButtonProps> = ({ buttonState, onClick, label }) => {
  const isLoading = buttonState === ButtonState.Loading;
  return (
    <div className="d-flex justify-content-center mt-5">
      <InternalButton onClick={onClick} variant="primary" size="sm">
        {isLoading && (
          <Spinner as="span" animation="border" size="sm" role="status" aria-hidden="true" />
        )}
        {!isLoading && label}
      </InternalButton>
    </div>
  );
};

Button Component accepts three props:

  1. buttonState: Controls loading/normal state
  2. onClick: Handler function for click events
  3. label: Text displayed on the button

Custom Download Hook

The core logic for file downloads:

import { AxiosResponse } from "axios";
import { useRef, useState } from "react";

interface DownloadFileProps {
  readonly apiDefinition: () => Promise<AxiosResponse<Blob>>;
  readonly preDownloading: () => void;
  readonly postDownloading: () => void;
  readonly onError: () => void;
  readonly getFileName: () => string;
}

interface DownloadedFileInfo {
  readonly download: () => Promise<void>;
  readonly ref: React.MutableRefObject<HTMLAnchorElement | null>;
  readonly name: string | undefined;
  readonly url: string | undefined;
}

export const useDownloadFile = ({
  apiDefinition,
  preDownloading,
  postDownloading,
  onError,
  getFileName,
}: DownloadFileProps): DownloadedFileInfo => {
  const ref = useRef<HTMLAnchorElement | null>(null);
  const [url, setFileUrl] = useState<string>();
  const [name, setFileName] = useState<string>();

  const download = async () => {
    try {
      preDownloading();
      const { data } = await apiDefinition();
      const url = URL.createObjectURL(new Blob([data]));
      setFileUrl(url);
      setFileName(getFileName());
      ref.current?.click();
      postDownloading();
      URL.revokeObjectURL(url);
    } catch (error) {
      onError();
    }
  };

  return { download, ref, url, name };
};

Hook Parameters Explained

  1. apiDefinition

    • Function containing API call
    • Returns Promise<AxiosResponse<Blob>>
    • Includes authentication headers
  2. preDownloading

    • Executed before API call
    • Used for UI state changes
    • Example: Setting button to loading
  3. postDownloading

    • Executed after successful download
    • Resets UI states
    • Example: Setting button back to primary
  4. onError

    • Called when API fails
    • Handles error scenarios
    • Example: Shows error alert
  5. getFileName

    • Generates download filename
    • Called after successful download
    • Example: Creates timestamp-based name

Download Function Flow

The download process follows these exact steps:

  1. Invoke preDownloading function

    • Triggers loading state
    • Prepares UI for download
  2. Call the API

    • Makes authenticated request
    • Handles blob response
    • Triggers onError if fails
  3. Create URL from response

    • Creates blob URL
    • Stores in state
    • Used in <a href...>
  4. Generate filename

    • Creates download filename
    • Stores in state
    • Used in download attribute
  5. Click hidden anchor

    • Uses ref to access DOM
    • Triggers actual download
  6. Post-download cleanup

    • Calls postDownloading
    • Resets UI state
    • Destroys generated URL

Implementation Component

Complete example showing hook usage:

import axios from "axios";
import React, { useState } from "react";
import { DateTime } from "luxon";
import { useDownloadFile } from "../../customHooks/useDownloadFile";
import { Button, ButtonState } from "../button";
import { Alert, Container } from "react-bootstrap";

export const DownloadSampleCsvFile: React.FC = () => {
  // State Management
  const [buttonState, setButtonState] = useState<ButtonState>(ButtonState.Primary);
  const [showAlert, setShowAlert] = useState<boolean>(false);

  // Handler Functions
  const preDownloading = () => setButtonState(ButtonState.Loading);
  const postDownloading = () => setButtonState(ButtonState.Primary);

  const onErrorDownloadFile = () => {
    setButtonState(ButtonState.Primary);
    setShowAlert(true);
    setTimeout(() => {
      setShowAlert(false);
    }, 3000);
  };

  const getFileName = () => {
    return DateTime.local().toISODate() + "_sample-file.csv";
  };

  const downloadSampleCsvFile = () => {
    return axios.get(
    "https://raw.githubusercontent.com/anubhav-goel/react-download-file-axios"
    + "/main/sampleFiles/csv-sample.csv",
      {
        responseType: "blob",
        headers: {
          Authorization: "Bearer <token>", // Add authentication token
        },
      },
    );
  };

  const { ref, url, download, name } = useDownloadFile({
    apiDefinition: downloadSampleCsvFile,
    preDownloading,
    postDownloading,
    onError: onErrorDownloadFile,
    getFileName,
  });

  return (
    <Container className="mt-5">
      <Alert variant="danger" show={showAlert}>
        Something went wrong. Please try again!
      </Alert>
      <a href={url} download={name} className="hidden" ref={ref} />
      <Button label="Download" buttonState={buttonState} onClick={download} />
    </Container>
  );
};

Component Breakdown

  1. State Variables

    • buttonState: Manages button loading state
    • showAlert: Controls error alert visibility
  2. Hook Functions

    • preDownloadFile: Sets loading state
    • postDownloadFile: Resets to primary state
    • onError: Shows error alert
    • apiDefinition: Makes authenticated API call
    • getFileName: Generates timestamp filename
  3. JSX Elements

The component returns three main JSX elements:

  1. Hidden Anchor Tag:

    <a href={url} download={name} className="hidden" ref={ref} />
    

    This hidden tag is not visible to the user. The download function internally clicks this button after the file is downloaded using refs.

  2. Download Button:

    <Button label="Download" buttonState={buttonState} onClick={download} />
    

    The button which users click to initiate the download.

    Normal Button State
    Normal Button State
    Loading Button State
    Loading Button State

    Different Button States

  3. Alert Component:

    <Alert variant="danger" show={showAlert}>
      Something went wrong. Please try again!
    </Alert>
    

    Responsible for showing error messages on the UI.

    Error State

    Error State Display

App Integration

Finally, integrate into your App:

import React from "react";
import { DownloadSampleCsvFile } from "./components/downloadSampleCsvFile";

const App: React.FC = () => {
  return <DownloadSampleCsvFile />;
};

export default App;
Technology Used
React + TypeScript
Axios
React Bootstrap
Results and Benefits
Using this custom hook approach, we achieved the following benefits:
Reusable Download Logic
Reusable Download Logic
The custom hook encapsulates all download-related logic, making it easily reusable across different components.
Type-Safe Implementation
Type-Safe Implementation
TypeScript integration ensures type safety and better IDE support.
Progress Tracking
Progress Tracking
Built-in progress tracking functionality provides users with feedback during downloads.
Best Practices and Recommendations
Proper Error Handling
Proper Error Handling
Implement comprehensive error handling with user feedback for failed downloads.
Resource Cleanup
Resource Cleanup
Always clean up URLs and resources after download completion to prevent memory leaks.
Type Safety
Type Safety
Utilize TypeScript interfaces and proper typing for better code maintainability.

Testing

  1. Start the development server:
npm start
# or
yarn start
  1. Test different scenarios:
    • Normal download flow
    • Loading state display
    • Error handling
    • Authentication
    • File naming

Conclusion

This implementation provides a reusable solution for handling authenticated file downloads in React applications. The custom hook pattern encapsulates complexity while maintaining type safety and proper resource management.

For complete source code, visit the GitHub repository.

Blogs You Might Like
Tech Prescient
We unleash growth by helping our customers become data driven and secured with our Data and Identity solutions.
Social Media IconSocial Media Icon
Social Media IconSocial Media Icon
Glassdoor
OUR PARTNERS
AWS Partner
Azure Partner
Okta Partner
Databricks Partner

© 2017 - 2025 | Tech Prescient | All rights reserved.

Tech Prescient
Social Media IconSocial Media Icon
Social Media IconSocial Media Icon
We unleash growth by helping our customers become data driven and secured with our Data and Identity solutions.
OUR PARTNERS
AWS Partner
Azure Partner
Databricks Partner
Okta Partner
Glassdoor

© 2017 - 2025 | Tech Prescient | All rights reserved.

Tech Prescient
We unleash growth by helping our customers become data driven and secured with our Data and Identity solutions.
Social Media IconSocial Media Icon
Social Media IconSocial Media Icon
OUR PARTNERS
AWS Partner
Okta Partner
Azure Partner
Databricks Partner
Glassdoor

© 2017 - 2025 | Tech Prescient | All rights reserved.