How to Enable File Upload on React's Material Ui Simple Input?

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?

3

16 Answers

The API provides component for this purpose.

<Button
  variant="contained"
  component="label"
>
  Upload File
  <input
    type="file"
    hidden
  />
</Button>
2

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> 
5

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> 
2

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);
1

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>
0

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>
);
1

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>
    </>
  );
};

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>
  );
}

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>
 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>

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

<form id='uploadForm'
      action='
      method='post'
      encType="multipart/form-data">
    <input type="file" id="sampleFile" style="display: none;" />
    <Button htmlFor="sampleFile" component="label" type={'submit'}>Upload</Button> 
</form>
<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.

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>
  )

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" />

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();

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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

James H. Sterling

James H. Sterling

Environmental Science & Climate Journalist

James Sterling reports on renewable energy developments, climate policy, ecological conservation, and green tech innovations around the globe.

Share this article
Twitter Facebook Pinterest