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" style={{display: "none"}}/> </div> </div> ) }
}
export default ContentHere 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> 2 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 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" 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);
} 1 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> ) }
} 1 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 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) }} 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.