hooks/useDownloader.js

import { useRef, useEffect, MutableRefObject } from 'react';

import { defaultDownloadConfig, errorMessages } from '../constants';
import { DownloadService } from '../download-service/download.service';

const { defaultType, defaultEvent, defaultName } = defaultDownloadConfig;

/** @module Downloader */

/**
 * Object for inserting to html element
 * @interface
 * @global
 * @typedef {{ref: MutableRefObject<HTMLElement>}} DownloadRefContainer
 * @property {MutableRefObject<HTMLElement>} ref - Reference to html element.
 */

/**
 * Manual downloading config
 * @interface
 * @global
 * @typedef ManualDownloadConfig
 * @property data {string|Object} - Downloaded data. Required.
 * @property name {string} - Name of downloaded data. Optional. Default - "file"
 * @property type {string} - Type of downloaded data. Optional. Default - "text/plain"
 */

/**
 * Function to download file
 * @interface
 * @global
 * @typedef {function} DownloadFn
 * @param config {ManualDownloadConfig} Manual downloading config
 * @return {Promise<void>} Promises resolves after downloading file.
 */

/**
 * Object to manage downloading files
 * @interface
 * @global
 * @typedef DownloadManager
 * @property {DownloadRefContainer} downloader - Object for inserting to html element.
 * @property {DownloadFn} download - Function to download file.
 */

/**
 * Downloading config
 * @interface
 * @global
 * @typedef DownloadConfig
 * @property data {string|Object} - Downloaded data. Required.
 * @property name {string} - Name of downloaded data. Optional. Default - "file"
 * @property type {string} - Type of downloaded data. Optional. Default - "text/plain"
 * @property event {string} - Event that triggers downloading. Optional. Default - "click"
 * @property onDownloaded {function} - Function is called after successful downloading. Optional
 * @property onError {function} - Function is called after failed downloading. Optional
 */

/**
 * Hook for downloading data
 * @param config {DownloadConfig} - Downloading config.
 * @return {DownloadManager} - Object to manage downloading files.
 * @function
 */
export function useDownloader(
    { data, onDownloaded, onError, name = defaultName, type = defaultType, event = defaultEvent } = {}
) {

    /**
     * Instance of DownloadService
     * @type {DownloadService}
     */
    const downloadService = new DownloadService();

    /**
     * Reference to trigger (HTMLElement)
     */
    const triggerRef = useRef(null);

    /**
     * Dispatch event about error
     * @param error {Error} - Instance of Error object
     */
    const handleError = error => {
        if (onError && typeof onError === 'function') {
            return onError(error);
        }
    };

    /** \
     * Download file
     * @param config {ManualDownloadConfig} - Manual downloading config.
     * @return {Promise<void>} Promise resolves after downloading file
     */
    const download = async ({ data, name = defaultName, type = defaultType }) => {
        if (!data) throw new Error(errorMessages.noData);
        await downloadService.download(data, name, type);
    };

    /**
     * Handle trigger event
     */
    const handleTrigger = async () => {
        try {
            await download({ data, name, type });
            if (onDownloaded && typeof onDownloaded === 'function') {
                onDownloaded();
            }
        } catch (e) {
            handleError(e);
        }
    };

    /**
     * Hook for adding/removing trigger listener
     */
    useEffect(() => {
        const element = triggerRef.current;
        if (element) element.addEventListener(event, handleTrigger);
        return () => {
            if (element) element.removeEventListener(event, handleTrigger);
        };
    }, [triggerRef, event]); // eslint-disable-line

    return {
        downloader: { ref: triggerRef },
        download
    };

}