All files use-upload.ts

100% Statements 31/31
100% Branches 4/4
100% Functions 10/10
100% Lines 29/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                                                                  2x     22x         22x 8x   8x 8x   8x 7x           7x 1x 1x 2x             7x 1x           7x   2x 2x   2x     2x       2x   2x 2x     2x     7x 1x     7x 1x           7x     22x    
import { useState } from "react";
 
type Headers = {
  [key: string]: any;
};
 
type RequestOptions = {
  method: string;
  url: string;
  headers?: Headers;
  body: Document | BodyInit | null | undefined;
};
 
type BeforeRequestProps = {
  xhr: XMLHttpRequest;
  files: FileList;
};
type BeforeRequest = (
  props: BeforeRequestProps
) => Promise<RequestOptions | undefined> | RequestOptions | undefined;
 
type UploadProps = {
  files: FileList;
};
 
type UseUploadState<Response> = {
  loading: boolean;
  done: boolean;
 
  data?: Response;
  error?: ProgressEvent;
  xhr?: XMLHttpRequest;
  responseHeaders?: Headers;
  progress?: number;
};
 
type UseUploadResults<Response> = [
  (props: UploadProps) => void,
  UseUploadState<Response>
];
 
export const useUpload = <Response = any>(
  beforeRequest: BeforeRequest
): UseUploadResults<Response> => {
  let [state, setState] = useState<UseUploadState<Response>>({
    loading: false,
    done: false,
  });
 
  const upload = async ({ files }: UploadProps) => {
    setState({ loading: true, done: false });
 
    const xhr = new XMLHttpRequest();
    let options = await beforeRequest({ xhr, files });
    // bail out if you return undefined from options
    if (!options) return setState({ loading: false, done: false });
    xhr.open(options.method, options.url);
 
    /*
      Helper method for setting headers on an xhr request, one of the only
      extra features of this hook
    */
    if (options.headers) {
      let headers = options.headers;
      Object.keys(options.headers).forEach((header) =>
        xhr.setRequestHeader(header, headers[header])
      );
    }
 
    /*
      XHR Listeners
    */
    xhr.upload.addEventListener("progress", (event) => {
      setState((state) => ({
        ...state,
        progress: Math.round((event.loaded / event.total) * 100),
      }));
    });
 
    xhr.addEventListener("load", () => {
      let data;
      try {
        data = JSON.parse(xhr.response);
      } catch (e) {
        data = xhr.response;
      }
 
      let responseHeaders = xhr
        .getAllResponseHeaders()
        .trim()
        .split(/[\r\n]+/)
        .map((line) => line.split(": "))
        .reduce((acc: Headers, [header, value]) => {
          acc[header] = value;
          return acc;
        }, {});
 
      setState({ data, loading: false, xhr, responseHeaders, done: true });
    });
 
    xhr.addEventListener("error", (error) => {
      setState({ error, xhr, loading: false, done: true });
    });
 
    xhr.addEventListener("abort", (error) => {
      setState({ error, xhr, loading: false, done: true });
    });
 
    /*
      send the request!
    */
    xhr.send(options.body);
  };
 
  return [upload, state];
};