user16860065
user16860065

Reputation:

File upload with react and typescript

I am trying to build a simple single select upload component. On click of a button that has a hidden input field, I open the file dialog then I select a file. The placeholder changes to the file name and then a change and clear button. Everything works fine, but on click of clear the file dialog, I dont want it to open on clear. It should open when "Choose file to upload" and "Change" is clicked. Can someone help?.

I am using material UI for the same

Sandbox: https://codesandbox.io/s/react-hook-upload-oxqdp2?file=/src/Upload.tsx:0-1784

import * as React from "react";
import { Button } from "@material-ui/core";
import { useState } from "react";

interface UploaderProps {
  fileType?: string | AcceptedFileType[];
}

enum AcceptedFileType {
  Text = ".txt",
  Gif = ".gif",
  Jpeg = ".jpg",
  Png = ".png",
  Doc = ".doc",
  Pdf = ".pdf",
  AllImages = "image/*",
  AllVideos = "video/*",
  AllAudios = "audio/*"
}

export const Upload = (props: UploaderProps) => {
  const { fileType } = props;
  const acceptedFormats: string | AcceptedFileType[] =
    typeof fileType === "string"
      ? fileType
      : Array.isArray(fileType)
      ? fileType?.join(",")
      : AcceptedFileType.Text;
  const [selectedFiles, setSelectedFiles] = useState<File | undefined>(
    undefined
  );

  const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedFiles(event?.target?.files?.[0]);
  };

  const onUpload = () => {
    console.log(selectedFiles);
  };

  return (
    <>
      <Button
        variant="contained"
        component="label"
        style={{ textTransform: "none" }}
      >
        <input
          hidden
          type="file"
          accept={acceptedFormats}
          onChange={handleFileSelect}
        />
        {!selectedFiles?.name && <span> Choose file to upload</span>}
        {selectedFiles?.name && (
          <>
            <span style={{ float: "left" }}> {selectedFiles?.name}</span>
            <span style={{ padding: "10px" }}> Change</span>
            <span onClick={() => setSelectedFiles(undefined)}>Clear</span>
          </>
        )}
      </Button>
      <Button
        color="primary"
        disabled={!selectedFiles}
        style={{ textTransform: "none" }}
        onClick={onUpload}
      >
        Upload
      </Button>
    </>
  );
};

Upvotes: 1

Views: 5826

Answers (2)

sajan
sajan

Reputation: 1370

I would use useRef hook to refer to the hidden input field, something like this for example:

import * as React from 'react';
import Button from '@mui/material/Button';

const AcceptedFileType = {
  Text: '.txt',
  Gif: '.gif',
  Jpeg: '.jpg',
  Png: '.png',
  Doc: '.doc',
  Pdf: '.pdf',
  AllImages: 'image/*',
  AllVideos: 'video/*',
  AllAudios: 'audio/*',
};

export default function Upload({ fileType }) {
  const fileRef = React.useRef();
  const acceptedFormats =
    typeof fileType === 'string'
      ? fileType
      : Array.isArray(fileType)
      ? fileType?.join(',')
      : AcceptedFileType.Text;

  const [selectedFiles, setSelectedFiles] = React.useState();

  const handleFileSelect = (event) => {
    setSelectedFiles(event?.target?.files?.[0]);
  };

  const onUpload = () => {
    console.log(selectedFiles);
  };

  const onClear = () => {
    setSelectedFiles(undefined);
  };

  const onUpdate = (event) => {
    if (event.target.textContent.trim().toLowerCase() === 'change') {
      onClear();
      fileRef.current.click();
      return;
    }
    if (event.target.textContent.trim().toLowerCase() === 'clear') {
      onClear();
      return;
    }
  };

  return (
    <>
      <input
        ref={fileRef}
        hidden
        type="file"
        accept={acceptedFormats}
        onChange={handleFileSelect}
      />
      {!selectedFiles?.name && (
        <Button
          variant="contained"
          component="label"
          style={{ textTransform: 'none' }}
          onClick={() => fileRef.current?.click()}
        >
          Choose file to upload
        </Button>
      )}
      {selectedFiles?.name && (
        <Button
          variant="contained"
          component="label"
          style={{ textTransform: 'none' }}
          onClick={onUpdate}
        >
          <span style={{ float: 'left' }}> {selectedFiles?.name}</span>
          <span style={{ padding: '10px' }}> Change</span>
          <span>Clear</span>
        </Button>
      )}
      <Button
        color="primary"
        disabled={!selectedFiles}
        style={{ textTransform: 'none' }}
        onClick={onUpload}
      >
        Upload
      </Button>
    </>
  );
}

See working demo: https://stackblitz.com/edit/react-dmzlsq?file=demo.js

Upvotes: 0

ruckie
ruckie

Reputation: 149

You should prevent default behavior of event. It worked for me like this:

<span onClick={(e) => { e.preventDefault(); setSelectedFiles(undefined); }}>Clear</span>

Upvotes: 0

Related Questions