Как включить загрузку файлов при простом вводе React's Material UI?
Я создаю простую форму, чтобы загрузить файл, используя электронно-реактивный шаблон с интерфейсом формы и материала.
Проблема в том, что я не знаю, как создать поле входного файла, потому что пользовательский интерфейс не поддерживает ввод загружаемого файла.
Есть идеи, как этого добиться?
24 ответа
Вы должны обернуть свой вклад с компонента, и добавьте свойство containerElement со значением 'label'...
<RaisedButton
containerElement='label' // <-- Just add me!
label='My Label'>
<input type="file" />
</RaisedButton>
Вы можете прочитать больше об этом в этом выпуске GitHub.
Другое решение>=v1.0, которое более конструктивно для меня, чем решение @galki.
RaisedButton
устарела в>=v1.0. Я не могу найти больше информации о containerElement
(это может быть недокументировано?), но текущий API предоставляет component
для этого.
<Button
variant="contained"
component="label"
>
Upload File
<input
type="file"
style={{ display: "none" }}
/>
</Button>
Более новый пример v1.0.0:
<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>
Вот пример использования IconButton для захвата ввода (захват фото / видео) с использованием 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);
У меня это работает ("@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>
Если вы используете компоненты функции React и вам не нравится работать с ярлыками или идентификаторами, вы также можете использовать ссылку.
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>
);
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>
);
}
Ноя 2020
С помощью Material-UI и 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>
</>
);
};
Вы можете использовать компоненты Input и InputLabel пользовательского интерфейса материала. Вот пример, если вы использовали их для ввода файлов электронных таблиц.
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>
Машинописная версия javascript-решения @tomatentobi
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>
</>
);
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>
Это сработало для меня.
<Button variant="contained" component="label" >
UPLOAD
<input accept="image/*" hidden type="file" />
</Button>
<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>
Убедитесь, что у Button есть component = "span", это мне помогло.
Точно так же, как и должно быть, но измените компонент кнопки, чтобы он был подписан так
<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>
Вот пример:
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>
)
Если вы хотите, чтобы входной файл выглядел и вел себя так же, как обычный ввод:
...вы можете использовать обычныйTextField
компонент и поместите<input type="file"... />
внутри его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,
}} />
),
}} />
Вы можете добавитьonKeyDown
прослушиватель, чтобы открыть средство выбора файлов или очистить файл с помощью клавиатуры (когда ввод текста сфокусирован):
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()
}, [])
другой способ, и мы можем добавить имя файла в качестве значения для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>
)
}}
/>
Или есть библиотека для 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} />
}
Я использовал следующий трюк, у меня он работает.
<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>
Оба метода @galki и @elijahcarrel работают нормально. Если кто-то пытается выполнить модульное тестирование (шутка) для этих двух ответов.
Вы не сможете использовать компонент кнопки с
(specially if you are using disabled=true
expect(getByRole("button", {name: "Upload"})).not.toBeEnabled();
вместо этого используйте это
ожидать(getByLabelText("Загрузить")).not.toBeEnabled();
Вы можете продолжить все комментарии выше, это действительно здорово, однако у меня есть еще один вариант настройки вашего компонента, если вы хотите следовать.
// Импорт
import { styled } from '@mui/material/styles';
import { Input } from "@mui/material";
// Пользовательский стиль
const CustomFileInput = styled(Input)(({ theme }) => {
return {
color: "white",
'::before': {
border: 'none',
position: 'static',
content: 'none'
},
'::after': {
border: 'none',
position: 'static',
content: 'none'
}
}
});
// Использование этого компонента
<CustomFileInput type="file" />
Попробуй это
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
или
yarn add mui-file-input
yarn add @mui/icons-material
Это для выбора файла изображения
<IconButton color="primary" component="label">
<input type="file" accept="image/*" hidden />
<AttachFileIcon fontSize="medium" />
</IconButton>
ПРИМЕЧАНИЕ. Компонент пользовательского интерфейса React Material (IconButton, AttachFileIcon)
Во всех этих ответах не упоминается одна вещь: где прикрепить обработчик событий. Вы хотите прикрепить свой обработчик событий, но используетеref
при вводе, чтобы вы могли получить доступ к файлу.Button
элементы не дают вам доступа к файлу
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>