Reputation: 1921
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
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
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
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
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
Reputation: 21161
If you want your file input to look and behave just like a regular input:
...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
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
Reputation: 29
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
Reputation: 31
This worked for me.
<Button variant="contained" component="label" >
UPLOAD
<input accept="image/*" hidden type="file" />
</Button>
Upvotes: 3
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
Reputation: 1790
Try This
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
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
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
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
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
Reputation: 559
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
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
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
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
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
Reputation: 4117
The APIĀ provides component
for this purpose.
<Button
variant="contained"
component="label"
>
Upload File
<input
type="file"
hidden
/>
</Button>
Upvotes: 317
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
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
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
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
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
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
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