How to enable file upload on React's Material UI simple input?
Asked Answered
P

26

171

I am creating a simple form to upload file using electron-react-boilerplate with redux form & material ui.

The problem is that I do not know how to create input file field because material ui does not support upload file input.

Any ideas on how to achieve this?

Pahlavi answered 14/11, 2016 at 12:54 Comment(3)
This link has some code on how to do it kiranvj.com/blog/blog/file-upload-in-material-ui with a working codesandbox linkTamica
I added an answer as of 2020. https://mcmap.net/q/142931/-how-to-enable-file-upload-on-react-39-s-material-ui-simple-inputMistranslate
See material-ui.com/components/buttons/#upload-buttonPolanco
M
304

The API provides component for this purpose.

<Button
  variant="contained"
  component="label"
>
  Upload File
  <input
    type="file"
    hidden
  />
</Button>
Mcadams answered 4/1, 2019 at 17:35 Comment(6)
This is the best answer CSB -> codesandbox.io/s/react-file-upload-parse-csv-09plq1?file=/src/…Plastometer
This doesn't work for me. The below answer does the job: https://mcmap.net/q/142931/-how-to-enable-file-upload-on-react-39-s-material-ui-simple-inputEthno
Another way to do this - kiranvj.com/blog/blog/file-upload-in-material-uiTamica
This didnt work for me - I needed to use refs https://mcmap.net/q/142931/-how-to-enable-file-upload-on-react-39-s-material-ui-simple-inputAssure
Or there is library for MUI 5 / React 18 : viclafouch.github.io/mui-file-input 🔥Strobe
This "button" does not accept keyboard input, i.e. is really bad for accessibility. At the very least you'd need an additional onClick event handler on the button.Rf
I
131

Newer MUI version:

<input
  accept="image/*"
  className={classes.input}
  style={{ display: 'none' }}
  id="raised-button-file"
  multiple
  type="file"
  />
    <label htmlFor="raised-button-file">
    <Button variant="raised" component="span" className={classes.button}>
      Upload
    </Button>
</label> 
Insurance answered 21/3, 2018 at 14:7 Comment(6)
Or just <input hidden>Nett
Hello - Once a file is uploaded (ie when you highlight a file and click open on the popped-up form) an onChange event on the <input /> is called, but my file isn't available in the target property of the event object (or any property on the event object that I can see). Where is the file available?Roux
@jboxxx: the file(s) will be on target.files (input elements have a built in files attribute that lists every selected file)Talented
In the newest version variant="raised" is deprecated, it expects one of ["text","outlined","contained"]Hunterhunting
with this example because we set the component to span we lose accessibility in keyboard navigation.Telamon
@AlexZamai , That link is no longer workingLefevre
G
40

You need to wrap your input with component, and add containerElement property with value 'label' ...

<RaisedButton
   containerElement='label' // <-- Just add me!
   label='My Label'>
   <input type="file" />
</RaisedButton>

You can read more about it in this GitHub issue.

EDIT: Update 2019.

Check at the bottom answer from @galki

TLDR;

<input
  accept="image/*"
  className={classes.input}
  style={{ display: 'none' }}
  id="raised-button-file"
  multiple
  type="file"
/>
<label htmlFor="raised-button-file">
  <Button variant="raised" component="span" className={classes.button}>
    Upload
  </Button>
</label> 
Gerianne answered 14/11, 2016 at 13:9 Comment(2)
How to use this file object though? HTML FileReader doesn't seem to work :/ Considering, I used <input type='file' onChange='handleFile'> handleFile = file => { .. }Coletta
Check out @galki's answer at the bottom, if you're visiting this in 2019 :DMufinella
Y
35

Here's an example using an IconButton to capture input (photo/video capture) using v3.9.2:

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';
import Videocam from '@material-ui/icons/Videocam';

const styles = (theme) => ({
    input: {
        display: 'none'
    }
});

class MediaCapture extends Component {
    static propTypes = {
        classes: PropTypes.object.isRequired
    };

    state: {
        images: [],
        videos: []
    };

    handleCapture = ({ target }) => {
        const fileReader = new FileReader();
        const name = target.accept.includes('image') ? 'images' : 'videos';

        fileReader.readAsDataURL(target.files[0]);
        fileReader.onload = (e) => {
            this.setState((prevState) => ({
                [name]: [...prevState[name], e.target.result]
            }));
        };
    };

    render() {
        const { classes } = this.props;

        return (
            <Fragment>
                <input
                    accept="image/*"
                    className={classes.input}
                    id="icon-button-photo"
                    onChange={this.handleCapture}
                    type="file"
                />
                <label htmlFor="icon-button-photo">
                    <IconButton color="primary" component="span">
                        <PhotoCamera />
                    </IconButton>
                </label>

                <input
                    accept="video/*"
                    capture="camcorder"
                    className={classes.input}
                    id="icon-button-video"
                    onChange={this.handleCapture}
                    type="file"
                />
                <label htmlFor="icon-button-video">
                    <IconButton color="primary" component="span">
                        <Videocam />
                    </IconButton>
                </label>
            </Fragment>
        );
    }
}

export default withStyles(styles, { withTheme: true })(MediaCapture);
Yseulta answered 23/3, 2019 at 1:47 Comment(1)
The component="span" is definitely needed for IconButton, that's what I was missing!Padding
P
15

It is work for me ("@material-ui/core": "^4.3.1"):

    <Fragment>
        <input
          color="primary"
          accept="image/*"
          type="file"
          onChange={onChange}
          id="icon-button-file"
          style={{ display: 'none', }}
        />
        <label htmlFor="icon-button-file">
          <Button
            variant="contained"
            component="span"
            className={classes.button}
            size="large"
            color="primary"
          >
            <ImageIcon className={classes.extendedIcon} />
          </Button>
        </label>
      </Fragment>
Pyrethrin answered 6/10, 2019 at 19:6 Comment(0)
S
14

If you're using React function components, and you don't like to work with labels or IDs, you can also use a reference.

const uploadInputRef = useRef(null);

return (
  <Fragment>
    <input
      ref={uploadInputRef}
      type="file"
      accept="image/*"
      style={{ display: "none" }}
      onChange={onChange}
    />
    <Button
      onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
      variant="contained"
    >
      Upload
    </Button>
  </Fragment>
);
Shower answered 18/9, 2020 at 11:0 Comment(2)
Worked for me - as noted above <input hidden> is simplerHebraist
Typescript version https://mcmap.net/q/142931/-how-to-enable-file-upload-on-react-39-s-material-ui-simple-inputAssure
M
13

Official recommendation

import * as React from 'react';
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import PhotoCamera from '@mui/icons-material/PhotoCamera';
import Stack from '@mui/material/Stack';

const Input = styled('input')({
  display: 'none',
});

export default function UploadButtons() {
  return (
    <Stack direction="row" alignItems="center" spacing={2}>
      <label htmlFor="contained-button-file">
        <Input accept="image/*" id="contained-button-file" multiple type="file" />
        <Button variant="contained" component="span">
          Upload
        </Button>
      </label>
      <label htmlFor="icon-button-file">
        <Input accept="image/*" id="icon-button-file" type="file" />
        <IconButton color="primary" aria-label="upload picture" component="span">
          <PhotoCamera />
        </IconButton>
      </label>
    </Stack>
  );
}
Mahone answered 16/3, 2022 at 13:8 Comment(1)
This works when you click with the mouse, but it appears to disregard keyboard enter keyevent that would normally trigger a click :'(. It works if you also add the ref and click trigger - https://mcmap.net/q/142931/-how-to-enable-file-upload-on-react-39-s-material-ui-simple-inputWhitley
M
12

Nov 2020

With Material-UI and React Hooks

import * as React from "react";
import {
  Button,
  IconButton,
  Tooltip,
  makeStyles,
  Theme,
} from "@material-ui/core";
import { PhotoCamera } from "@material-ui/icons";

const useStyles = makeStyles((theme: Theme) => ({
  root: {
    "& > *": {
      margin: theme.spacing(1),
    },
  },
  input: {
    display: "none",
  },
  faceImage: {
    color: theme.palette.primary.light,
  },
}));

interface FormProps {
  saveFace: any; //(fileName:Blob) => Promise<void>, // callback taking a string and then dispatching a store actions
}

export const FaceForm: React.FunctionComponent<FormProps> = ({ saveFace }) => {

  const classes = useStyles();
  const [selectedFile, setSelectedFile] = React.useState(null);

  const handleCapture = ({ target }: any) => {
    setSelectedFile(target.files[0]);
  };

  const handleSubmit = () => {
    saveFace(selectedFile);
  };

  return (
    <>
      <input
        accept="image/jpeg"
        className={classes.input}
        id="faceImage"
        type="file"
        onChange={handleCapture}
      />
      <Tooltip title="Select Image">
        <label htmlFor="faceImage">
          <IconButton
            className={classes.faceImage}
            color="primary"
            aria-label="upload picture"
            component="span"
          >
            <PhotoCamera fontSize="large" />
          </IconButton>
        </label>
      </Tooltip>
      <label>{selectedFile ? selectedFile.name : "Select Image"}</label>. . .
      <Button onClick={() => handleSubmit()} color="primary">
        Save
      </Button>
    </>
  );
};

Mistranslate answered 16/11, 2020 at 10:24 Comment(0)
U
5

You can use Material UI's Input and InputLabel components. Here's an example if you were using them to input spreadsheet files.

import { Input, InputLabel } from "@material-ui/core";

const styles = {
  hidden: {
    display: "none",
  },
  importLabel: {
    color: "black",
  },
};

<InputLabel htmlFor="import-button" style={styles.importLabel}>
    <Input
        id="import-button"
        inputProps={{
          accept:
            ".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
        }}
        onChange={onInputChange}
        style={styles.hidden}
        type="file"
    />
    Import Spreadsheet
</InputLabel>
Unanimity answered 28/10, 2020 at 15:34 Comment(0)
A
5

Typescript version of @tomatentobi's javascript solution

const uploadInputRef = useRef<HTMLInputElement | null>(null);

return (
  <>
    <input
      ref={uploadInputRef}
      type="file"
      accept="image/*"
      style={{ display: "none" }}
      onChange={onChange}
    />
    <Button
      onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
      variant="contained">
      Upload
    </Button>
  </>
);
Assure answered 26/9, 2022 at 5:52 Comment(0)
H
3
 import AddPhotoIcon from "@mui/icons-material/AddAPhoto";
 import Fab from "@mui/material/Fab";

  <Fab color="primary" aria-label="add-image" sx={{ position: "fixed", bottom: 16, right: 16, overflow: "hidden" }}>
    <input
      type="file"
      onChange={imageHandler}
      accept=".jpg, .jpeg, .png"
      accept="image/*"
      multiple
      style={{ //make this hidden and display only the icon
        position: "absolute", 
        top: "-35px",
        left: 0,
        height: "calc(100% + 36px)",
        width: "calc(100% + 5px)",
        outline: "none",
      }}
    />

    <AddPhotoIcon />
  </Fab>
Hostler answered 29/3, 2022 at 19:51 Comment(0)
S
3

This worked for me.

          <Button variant="contained" component="label" >
              UPLOAD
              <input accept="image/*" hidden type="file" />
          </Button>
Songstress answered 20/1, 2023 at 18:20 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Pharmacopoeia
This is a correct answer.Roush
B
2

Just the same as what should be but change the button component to be label like so

<form id='uploadForm'
      action='http://localhost:8000/upload'
      method='post'
      encType="multipart/form-data">
    <input type="file" id="sampleFile" style="display: none;" />
    <Button htmlFor="sampleFile" component="label" type={'submit'}>Upload</Button> 
</form>
Buchholz answered 20/5, 2020 at 19:49 Comment(0)
T
2
<input type="file"
               id="fileUploadButton"
               style={{ display: 'none' }}
               onChange={onFileChange}
        />
        <label htmlFor={'fileUploadButton'}>
          <Button
            color="secondary"
            className={classes.btnUpload}
            variant="contained"
            component="span"
            startIcon={
              <SvgIcon fontSize="small">
                <UploadIcon />
              </SvgIcon>
            }
          >

            Upload
          </Button>
        </label>

Make sure Button has component="span", that helped me.

Tredecillion answered 28/9, 2020 at 14:39 Comment(0)
C
2

Here an example:

return (
    <Box alignItems='center' display='flex' justifyContent='center' flexDirection='column'>
      <Box>
        <input accept="image/*" id="upload-company-logo" type='file' hidden />
        <label htmlFor="upload-company-logo">
          <Button component="span" >
            <Paper elevation={5}>
              <Avatar src={formik.values.logo} className={classes.avatar} variant='rounded' />
            </Paper>
          </Button>
        </label>
      </Box>
    </Box>
  )

Christianize answered 11/1, 2021 at 12:53 Comment(0)
K
1

If you want your file input to look and behave just like a regular input:

enter image description here

...you can use a regular TextField component and place a <input type="file"... /> inside its endAdornment:

    <TextField
      name="file"
      value={ value.name }
      onChange={ handleFileChange }
      error={ error }
      readOnly
      InputProps={{
        endAdornment: (
          <input
            ref={ inputRef }
            type="file"
            accept="application/JSON"
            onChange={ handleFileChange }
            tabIndex={ -1 }
            style={{
              position: 'absolute',
              top: 0,
              right: 0,
              bottom: 0,
              left: 0,
              opacity: 0,
            }} />
        ),
      }} />

You can add an onKeyDown listener to open the file picker or clear the file using the keyboard (when the text input is focused):

const handleKeyDow = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
  const inputElement = inputRef.current

  if (!inputElement) return

  let preventDefault = true

  if (e.key === ' ' || e.key === 'Enter') {
    inputElement.click()
  } else if (e.key === 'Delete' || e.key === 'Backspace') {
    inputElement.value = ''
  } else {
    preventDefault = false
  }

  if (preventDefault) e.preventDefault()
}, [])
Kinsella answered 26/6, 2023 at 11:17 Comment(0)
E
0

You can pursue all the comments above, those are really great, However, I have another option for customizing your component, if you want to follow.

// Import

import { styled } from '@mui/material/styles';
import { Input } from "@mui/material";

// Custom style

const CustomFileInput = styled(Input)(({ theme }) => {
  return {
    color: "white",
    '::before': {
      border: 'none',
      position: 'static',
      content: 'none'
    },
    '::after': {
      border: 'none',
      position: 'static',
      content: 'none'
    }
  }
});

// Using that component

<CustomFileInput type="file" />
Endometriosis answered 4/2, 2022 at 3:51 Comment(0)
B
0

Both @galki and @elijahcarrel method works fine. If anyone trying to do unit-testing(jest) for these two answers.

You wont be able to use the button component with (specially if you are using disabled=true

expect(getByRole("button", {name: "Upload"})).not.toBeEnabled();

instead use this

expect(getByLabelText("Upload")).not.toBeEnabled();
Babysitter answered 21/2, 2022 at 16:48 Comment(0)
D
0

This is for Select Image File

<IconButton color="primary" component="label">
   <input type="file" accept="image/*" hidden />
   <AttachFileIcon fontSize="medium" />
</IconButton>

NOTE : React Material UI Component (IconButton, AttachFileIcon)

Duramen answered 22/5, 2022 at 7:7 Comment(0)
E
0

another way is this and we can add the name of the file as a value for TextField.

<TextField
    value={state.value}
    label="upload profile picture"
    sx={{ m: 1, width: '25ch' }}
    InputProps={{
        fullWidth: true,
        startAdornment: (
            <IconButton component="label">
                <AttachFileIcon />
                <input
                    type="file"
                    hidden
                    onChange={handleUploadInput}
                    name="[name]"
                />
            </IconButton>
        )
    }}
/>
Evulsion answered 4/9, 2022 at 11:14 Comment(0)
S
0

Or there is library for MUI 5 / React 18 : https://viclafouch.github.io/mui-file-input/ 🔥

import React from 'react'
import { MuiFileInput } from 'mui-file-input'

const MyComponent = () => {
  const [value, setValue] = React.useState(null)

  const handleChange = (newValue) => {
    setValue(newValue)
  }

  return <MuiFileInput value={value} onChange={handleChange} />
}
Strobe answered 18/10, 2022 at 7:38 Comment(0)
R
0

Try This

enter image description here enter image description here

import React from 'react'
import { MuiFileInput } from 'mui-file-input'

export default function MyComponent () {
  const [file, setFile] = React.useState(null)

  const handleChange = (newFile) => {
    setFile(newFile)
  }

  return (
    <MuiFileInput value={file} onChange={handleChange} />
  )
}
npm install mui-file-input --save
npm install @mui/icons-material

or

yarn add mui-file-input
yarn add @mui/icons-material
Rhodian answered 1/11, 2022 at 11:55 Comment(0)
D
0

I used the following trick, it works for me.

<div className="relative">
   <TextField value={field.value} variant="standard" label="Image" fullWidth />
   <input
   ref={fileRef}
   type="file"
   accept=".png, .webp"
   onChange={async (event) => {
   try {
   const file = (event.target as HTMLInputElement).files?.item(0);
   field.onChange(file?.name);
   } catch (err) {}
   }}
   className="w-full absolute inset-0 opacity-0"
   />
</div>
Decemvirate answered 8/4, 2023 at 1:56 Comment(0)
S
0

In case someone is using NextJs with Material UI.

<Controller
      name="fileInput"
      control={control}
      defaultValue=""
      render={({ field: { onChange, onBlur, value, ref } }) => (
        <>
          <input
            onChange={onChange}
            onBlur={onBlur}
            ref={ref}
            type="file"
            hidden
            id="file-upload"
          />
          <label htmlFor="file-upload">
            <Button
              component="button"
              variant="contained"
              startIcon={<CloudUploadIcon />}
            >
              Upload file
            </Button>
          </label>
        </>
      )}
    />
Strasser answered 2/2, 2024 at 9:9 Comment(0)
A
0

if you were looking for a textfield type file uploader like i was but didn't, this is what i managed to piece up you can use this enter image description here

i used an AutoComplete Component of Material UI and made changes to that.

Reusable Component:

import { Close, FileUploadOutlined } from "@mui/icons-material";
import { Autocomplete, ButtonBase, TextField } from "@mui/material";
import React, { Fragment, useRef } from "react";

const FileField = ({
  textfieldProps,
  autoCompleteProps,
  multiple,
  files,
  setFiles,
}) => {
  const fileRef = useRef(null);
  const handleCarouselFiles = (e) => {
    const selectedFiles = e.target.files;
    if (multiple) {
      setFiles((prevFiles) => [...prevFiles, ...selectedFiles]);
    } else {
      setFiles(selectedFiles);
    }
  };
  const handleCarouselInput = () => {
    fileRef.current.click();
  };
  return (
    <Fragment>
      <Autocomplete
        multiple
        options={Array.from(files)}
        getOptionLabel={(option) => option.name}
        renderInput={(params) => (
          <TextField
            {...params}
            {...(textfieldProps ?? {})}
            disabled
            onClick={handleCarouselInput}
            InputProps={{
              ...params.InputProps,
              endAdornment: (
                <Fragment>
                  {files.length > 0 && (
                    <ButtonBase
                      onClick={(e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        setFiles([]);
                      }}
                      sx={{
                        paddingRight: "0.5rem",
                      }}
                    >
                      <Close />
                    </ButtonBase>
                  )}
                  <ButtonBase>
                    <FileUploadOutlined onClick={handleCarouselInput} />
                  </ButtonBase>
                </Fragment>
              ),
            }}
            sx={{
              color: "inherit",
              "& .MuiInputBase-root , & .MuiInputBase-input": {
                paddingRight: "1rem !important",
                cursor: "pointer",
              },
            }}
          />
        )}
        value={Array.from(files)}
        onChange={(event, newValue) => {
          event.preventDefault();
          setFiles(newValue);
        }}
        open={false}
        sx={{
          caretColor: "transparent",
          cursor: "pointer",
          "& .Mui-disabled,& .MuiInputLabel-root": {
            color: "rgba(0,0,0,0.6)",
            backgroundColor: "transparent",
          },
        }}
        {...(autoCompleteProps ?? {})}
      />
      <input
        type="file"
        ref={fileRef}
        style={{ display: "none" }}
        onChange={handleCarouselFiles}
        multiple={multiple}
      />
    </Fragment>
  );
};

export default FileField;

Usage:

import { Fragment, useState } from "react";
import "./App.css";
import FileField from "./FileField";

function App() {
  const [profile, setProfile] = useState([]);
  const [coverPhotos, setcoverPhotos] = useState([]);

  return (
    <Fragment>
      <div className="p-5">
        <FileField
          textfieldProps={{ label: "Single" }}
          autoCompleteProps={{ className: "my-5" }}
          files={profile}
          setFiles={setProfile}
        />
        <FileField
          textfieldProps={{ label: "Multiple" }}
          autoCompleteProps={{ className: "my-5" }}
          files={coverPhotos}
          setFiles={setcoverPhotos}
          multiple={true}
        />
      </div>
    </Fragment>
  );
}

export default App;

Explaination:

The textfieldProps are any props that you would pass down to normal textfield and similarly for autoCompleteProps , the files itself are taken as props as an array along with a setter function to set the value onChange

Links:

Github here

CodeSandbox here

Hopefully this helps

Ameba answered 15/3, 2024 at 11:41 Comment(0)
P
-1

One thing all of these answers didn't mention is where to attach your event handler. You want to attach your event handler to Button but use a ref on input, so that you can access the file. Button elements do not give you access to the file

const fileUpload=useRef();

const handleFileUpload = () =>{
   const file = fileRef.current.files?.[0];
  //...do whatever else you need here
}

<Button
  variant="contained"
  component="label"
  onClick={handleFileUpload}
>
  Upload File
  <input
    type="file"
    hidden
    ref={fileRef}
  />
</Button>
Puffball answered 25/5, 2023 at 22:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.