gintoki27
gintoki27

Reputation: 1921

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?

Upvotes: 177

Views: 415862

Answers (26)

Abdullah Abid
Abdullah Abid

Reputation: 1661

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

Upvotes: 0

galki
galki

Reputation: 8715

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> 

Upvotes: 133

Tajs
Tajs

Reputation: 621

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

Upvotes: 0

Danziger
Danziger

Reputation: 21161

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()
}, [])

Upvotes: 1

guest
guest

Reputation: 2224

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>

Upvotes: -1

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>

Upvotes: 0

Aflah PS
Aflah PS

Reputation: 31

This worked for me.

          <Button variant="contained" component="label" >
              UPLOAD
              <input accept="image/*" hidden type="file" />
          </Button>

Upvotes: 3

Amir Rezvani
Amir Rezvani

Reputation: 1504

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

Upvotes: 0

Merrin K
Merrin K

Reputation: 1790

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

Upvotes: 0

Victor dlf
Victor dlf

Reputation: 346

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

Upvotes: 0

jamesioppolo
jamesioppolo

Reputation: 467

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

Upvotes: 5

Apu Pradhan
Apu Pradhan

Reputation: 101

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)

Upvotes: 0

Chukwuemeka Maduekwe
Chukwuemeka Maduekwe

Reputation: 8526

 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>

Upvotes: 3

Kutalia
Kutalia

Reputation: 559

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

Upvotes: 14

Suvesh
Suvesh

Reputation: 71

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

Upvotes: 0

MD SHAYON
MD SHAYON

Reputation: 8055

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

Upvotes: 0

ahmnouira
ahmnouira

Reputation: 3401

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

Upvotes: 2

DevLoverUmar
DevLoverUmar

Reputation: 13933

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

Upvotes: 12

elijahcarrel
elijahcarrel

Reputation: 4117

The APIĀ provides component for this purpose.

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

Upvotes: 317

Jonri2
Jonri2

Reputation: 51

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>

Upvotes: 5

Richard Lee
Richard Lee

Reputation: 2245

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

Upvotes: 2

tomatentobi
tomatentobi

Reputation: 3157

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

Upvotes: 14

Liam
Liam

Reputation: 6743

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>

Upvotes: 2

Alexei Zababurin
Alexei Zababurin

Reputation: 957

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>

Upvotes: 15

vedran
vedran

Reputation: 1118

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> 

Upvotes: 40

Markus Hay
Markus Hay

Reputation: 1010

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

Upvotes: 35

Related Questions