My react component:
import React, { PropTypes, Component } from 'react'
class Content extends Component {
handleClick(e) {
console.log("Hellooww world")
}
render() {
return (
<div className="body-content">
<div className="add-media" onClick={this.handleClick.bind(this)}>
<i className="plus icon"></i>
<input type="file" id="file" style={{display: "none"}}/>
</div>
</div>
)
}
}
export default Content
Here when I click a div with icon I want to open a <input> file which shows me option to select photos. After selecting the photos I want to get the value which photo is selected. How can I do this in react ??
9 Answers
Upload a new answer using React Hooks
First create your Input ref hook
const inputFile = useRef(null)
Then set it to your INPUT and add a style to display:none for the input will not show in the screen
<input type='file' id='file' ref={inputFile} style={{display: 'none'}}/>
Then create your function to handle the open file, the function should be inside the same function you are using the useRef Hook
const onButtonClick = () => {
// `current` points to the mounted file input element
inputFile.current.click();
};
Then set the funcion to a Button element
<button onClick={onButtonClick}>Open file upload window</button>
Other than just put the input on you view, you will need handle the change of input content. To do this implement onChange, and get opened file info, like this:
<input id="myInput"
type="file"
ref={(ref) => this.upload = ref}
style={{display: 'none'}}
onChange={this.onChangeFile.bind(this)}
/>
<RaisedButton
label="Open File"
primary={false}
onClick={()=>{this.upload.click()}}
/>
onChangeFile(event) {
event.stopPropagation();
event.preventDefault();
var file = event.target.files[0];
console.log(file);
this.setState({file}); /// if you want to upload latter
}
Console will output:
File {
name: "my-super-excel-file.vcs",
lastModified: 1503267699000,
lastModifiedDate: Sun Aug 20 2017 19:21:39 GMT-0300 (-03),
webkitRelativePath: "",
size: 54170,
type:"application/vnd.vcs"
}
And now you can work with it like you want. But, if you want to UPLOAD it, you must begin with:
var form = new FormData();
form.append('file', this.state.file);
YourAjaxLib.doUpload('/yourEndpoint/',form).then(result=> console.log(result));
Add the ref attribute to your input:
<input type="file" id="file" ref="fileUploader" style={{display: "none"}}/>
Change the handleClick function:
handleClick(e) {
this.refs.fileUploader.click();
}
Since you are using ES6, you will need to bind this to your handleClick function, and we can do that in the constructor:
constructor (props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
React 16.3 provides better approach, using React.createRef() method. See
Typescript example:
export class MainMenu extends React.Component<MainMenuProps, {}> {
private readonly inputOpenFileRef : RefObject<HTMLInputElement>
constructor() {
super({})
this.inputOpenFileRef = React.createRef()
}
showOpenFileDlg = () => {
this.inputOpenFileRef.current.click()
}
render() {
return (
<div>
<input ref={this.inputOpenFileRef} type="file" style={{ display: "none" }}/>
<button onClick={this.showOpenFileDlg}>Open</Button>
</div>
)
}
}
All suggested answers are great. I went beyond and allow the user to add image and preview it right away. I used React hooks.
Thanks for everyone's support
Result should look like the followingimport React, { useEffect, useRef, useState } from 'react';
// Specify camera icon to replace button text
import camera from '../../../assets/images/camera.svg'; // replace it with your path
// Specify your default image
import defaultUser from '../../../assets/images/defaultUser.svg'; // replace it with your path
// Profile upload helper
const HandleImageUpload = () => {
// we are referencing the file input
const imageRef = useRef();
// Specify the default image
const [defaultUserImage, setDefaultUserImage] = useState(defaultUser);
// On each file selection update the default image
const [selectedFile, setSelectedFile] = useState();
// On click on camera icon open the dialog
const showOpenFileDialog = () => {
imageRef.current.click();
};
// On each change let user have access to a selected file
const handleChange = (event) => {
const file = event.target.files[0];
setSelectedFile(file);
};
// Clean up the selection to avoid memory leak
useEffect(() => {
if (selectedFile) {
const objectURL = URL.createObjectURL(selectedFile);
setDefaultUserImage(objectURL);
return () => URL.revokeObjectURL(objectURL);
}
}, [selectedFile]);
return {
imageRef,
defaultUserImage,
showOpenFileDialog,
handleChange,
};
};
// Image component
export const ItemImage = (props) => {
const {itemImage, itemImageAlt} = props;
return (
<>
<img
src={itemImage}
alt={itemImageAlt}
className="item-image"
/>
</>
);
};
// Button with icon component
export const CommonClickButtonIcon = (props) => {
const {
onHandleSubmitForm, iconImageValue, altImg,
} = props;
return (
<div className="common-button">
<button
type="button"
onClick={onHandleSubmitForm}
className="button-image"
>
<img
src={iconImageValue}
alt={altImg}
className="image-button-img"
/>
</button>
</div>
);
};
export const MainProfileForm = () => {
const {
defaultUserImage,
handleChange,
imageRef,
showOpenFileDialog,
} = HandleImageUpload();
return (
<div className="edit-profile-container">
<div className="edit-profile-image">
<ItemImage
itemImage={defaultUserImage}
itemImageAlt="user profile picture"
/>
<CommonClickButtonIcon // Notice I omitted the text instead used icon
onHandleSubmitForm={showOpenFileDialog}
iconImageValue={camera}
altImg="Upload image icon"
/>
<input
ref={imageRef}
type="file"
style={{ display: 'none' }}
accept="image/*"
onChange={handleChange}
/>
</div>
</div>
);
};
My CSS
.edit-profile-container {
position: relative;
}
.edit-profile-container .edit-profile-image {
position: relative;
width: 200px;
display: flex;
justify-content: center;
}
.edit-profile-container .edit-profile-image .item-image {
height: 160px;
width: 160px;
border-radius: 360px;
}
.edit-profile-container .edit-profile-image .common-button {
position: absolute;
right: 0;
top: 30px;
}
.edit-profile-container .edit-profile-image .common-button .button-image {
outline: none;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
}
.edit-profile-container .edit-profile-image .common-button .image-button-img {
height: 30px;
width: 30px;
box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
}
You can wrap a it in a label, when you click the label it clicks the dialog.
<div>
<label htmlFor="fileUpload">
<div>
<h3>Open</h3>
<p>Other stuff in here</p>
</div>
</label>
<input hidden id="fileUpload" type="file" accept="video/*" />
</div>
import React, { useRef, useState } from 'react'
...
const inputRef = useRef()
....
function chooseFile() {
const { current } = inputRef
(current || { click: () => {}}).click()
}
...
<input
onChange={e => {
setFile(e.target.files)
}}
id="select-file"
type="file"
ref={inputRef}
/>
<Button onClick={chooseFile} shadow icon="/upload.svg">
Choose file
</Button>
the unique code that works to me using next.js
I recently wanted to implement a similar feature with Material UI, this approach is similar to @Niyongabo implementation, except I am using the Material UI framework and leveraging the Avatar/Badge components.
I also resize the image before working with it.
import React, { useEffect, useRef } from "react";
import List from "@material-ui/core/List";
import t from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import { Avatar, Badge } from "@material-ui/core";
import withStyles from "@material-ui/core/styles/withStyles";
import IconButton from "@material-ui/core/IconButton";
import EditIcon from "@material-ui/icons/Edit";
import useTheme from "@material-ui/core/styles/useTheme";
import("screw-filereader");
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
form: {
display: "flex",
flexDirection: "column",
margin: "auto",
width: "fit-content"
},
input: {
fontSize: 15
},
large: {
width: theme.spacing(25),
height: theme.spacing(25),
border: `4px solid ${theme.palette.primary.main}`
}
}));
const EditIconButton = withStyles((theme) => ({
root: {
width: 22,
height: 22,
padding: 15,
border: `2px solid ${theme.palette.primary.main}`
}
}))(IconButton);
export const AvatarPicker = (props) => {
const [file, setFile] = React.useState("");
const theme = useTheme();
const classes = useStyles();
const imageRef = useRef();
const { handleChangeImage, avatarImage } = props;
useEffect(() => {
if (!file && avatarImage) {
setFile(URL.createObjectURL(avatarImage));
}
return () => {
if (file) URL.revokeObjectURL(file);
};
}, [file, avatarImage]);
const renderImage = (fileObject) => {
fileObject.image().then((img) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const maxWidth = 256;
const maxHeight = 256;
const ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
const width = (img.width * ratio + 0.5) | 0;
const height = (img.height * ratio + 0.5) | 0;
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
const resizedFile = new File([blob], file.name, fileObject);
setFile(URL.createObjectURL(resizedFile));
handleChangeImage(resizedFile);
});
});
};
const showOpenFileDialog = () => {
imageRef.current.click();
};
const handleChange = (event) => {
const fileObject = event.target.files[0];
if (!fileObject) return;
renderImage(fileObject);
};
return (
<List data-testid={"image-upload"}>
<div
style={{
display: "flex",
justifyContent: "center",
margin: "20px 10px"
}}
>
<div className={classes.root}>
<Badge
overlap="circle"
anchorOrigin={{
vertical: "bottom",
horizontal: "right"
}}
badgeContent={
<EditIconButton
onClick={showOpenFileDialog}
style={{ background: theme.palette.primary.main }}
>
<EditIcon />
</EditIconButton>
}
>
<Avatar alt={"avatar"} src={file} className={classes.large} />
</Badge>
<input
ref={imageRef}
type="file"
style={{ display: "none" }}
accept="image/*"
onChange={handleChange}
/>
</div>
</div>
</List>
);
};
AvatarPicker.propTypes = {
handleChangeImage: t.func.isRequired,
avatarImage: t.object
};
export default AvatarPicker;
If you are okay with using hooks this package will solve your problem without need to create input element. This package doesnt render any html input elements. You can simply add OnClick on div element.
Here is working demo:
import { useFilePicker } from "use-file-picker";
import React from "react";
export default function App() {
const [files, errors, openFileSelector] = useFilePicker({
multiple: true,
accept: ".ics,.pdf"
});
if (errors.length > 0) return <p>Error!</p>;
return (
<div>
<div
style={{ width: 200, height: 200, background: "red" }}
onClick={() => openFileSelector()}
>
Reopen file selector
</div>
<pre>{JSON.stringify(files)}</pre>
</div>
);
}
Invoking openFileSelector() opens browser file selector.
files properties:
lastModified: number;
name: string;
content: string;
I created this package to solve the same issue.