59

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

10 Answers 10

140

First, create ref hook for your input.

const inputFile = useRef(null) 
// or, for TypeScript
// const inputFile = useRef<HTMLInputElement | null>(null);

Then set it to your input and add a style to display: none for it, to hide it from 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 function to the button element:

<button onClick={onButtonClick}>Open file upload window</button>

API for HTML input file

Sign up to request clarification or add additional context in comments.

3 Comments

"The function should be inside the same function you are using the useRef Hook" can't be overstated. Thanks for the hint!
In case of TypeScript, how to deal with TS18047: inputFile.current is possibly null?
Use the '?' operator: inputFile.current?.click();
67

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

1 Comment

Not getting any log in the onChangeFile function
38

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

1 Comment

This is just to open the dialog.
15

React 16.3 provides better approach, using React.createRef() method. See https://reactjs.org/blog/2018/03/29/react-v-16-3.html#createref-api

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 Comment

I wouldn't add in Typescript to an answer as people are going to have problems with RefObject etc...
7

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 following

enter image description here

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

Comments

5

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>

Comments

3
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 enter image description here

1 Comment

you should add a semicolon after this line: const { current } = inputRef
3

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.

enter image description here

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;

Edit patient-pond-0ech0

Comments

2

The concept is really simple. I will explain it as clearly as I can. First look at the code below:

import React from 'react';

export default function App() {

const [inputFile, setInputFile] = React.useState<File | null>(null)
const inputFileRef = React.useRef<HTMLInputElement | null>(null);

const handleUploadImage = () => {
  if (!inputFileRef.current) return;
    inputFileRef.current?.click();
}

const handleFileUploadChange = 
  (event: React.ChangeEvent<HTMLInputElement>) => {
     if (!event.target.files) return;
     setInputFile(event.target.files[0]);
};

return (
<div className='App'>
  {inputFile ? (
    <div>
      <img
        id="edited-img"
        src={URL.createObjectURL(inputFile)}
        className="img-main"
      />
    </div>
  ) : (
    <>
      <input
        type="file"
        style={{ display: "none" }}
        onChange={handleFileUploadChange}
        accept="image/x-png,image/jpg,image/jpeg"
        ref={inputFileRef}
      />
      <div
        className="input-container"
        onClick={handleUploadImage}
      >
        <p className="image-text">Choose Image</p>
      </div>
    </>
  )}
</div>
);
}

Here I've created a component that accepts images and store it in state hook. The image is displyed based on the conditional rendering.

The handleUploadImage function is called when the user clicks the the div. This function prompts the input element with the help of useRef.

The handleFileUploadChange function is called when there is a change in input. This function updates the inputFile state.

Comments

0

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: https://codesandbox.io/s/admiring-hellman-g7p91?file=/src/App.js

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;

https://www.npmjs.com/package/use-file-picker

I created this package to solve the same issue.

1 Comment

I want to set my table data with my files content. How do I do that?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.