Uploading multiple images with React - reactjs

I would like to upload multiple images first by previewing them then submitting to send them off. I have encountered this: TypeError: Cannot read property 'files' of null. It also only lets me upload just one image.
I have created files: [] as means to mount the images for review before submitting.
Tried creating an interface files: File[] = file then declaring it in state but get a different error that file does not exist on type {}
import * as React from "react"
class ImageUpload extends React.Component {
state: {
files: []
}
fileSelectedHandler = (file: any) => {
let addedFiles = this.state.files.concat(file)
this.setState({ files: addedFiles })
console.log("upload file " + file.name)
}
render() {
return (
< form >
<div>
<h2>Upload images</h2>
</div>
<h3>Images</h3>
<input type="file" onChange={this.fileSelectedHandler} />
</form>
)
}
}
export default ImageUpload
I expect it to allow me to select multiple images and store them in the array before sending them off as a batch. Is this even the right way to do it? Any feedback is greatly appreciated.

In order to fix the error TypeError: Cannot read property 'files' of null. you need to change the state declaration
state = {
files: []
}
If you want to have the opportunity to select multiple files you can use multiple option
<input type="file" multiple onChange={this.fileSelectedHandler} />
Or if you want to select image one by one, your implementation should work, just fix the state declaration and use e.target.files to get selected file
class ImageUpload extends React.Component {
state = {
files: []
}
fileSelectedHandler = (e) => {
this.setState({ files: [...this.state.files, ...e.target.files] })
}
render() {
return (
<form>
<div><h2>Upload images</h2></div>
<h3>Images</h3>
<input type="file" multiple onChange={this.fileSelectedHandler} />
</form>
)
}
}
ReactDOM.render(<ImageUpload />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Updated Answer with Typescript as a sperate componnent
import React, { useState, useEffect } from "react";
const UploadAndDisplayImage = () => {
const [images, setImages] = useState([] as any);
const [imageURLS, setImageURLs] = useState([]);
useEffect(() => {
if (images.length < 1) return;
const newImageUrls: any = [];
images.forEach((image:any) => newImageUrls.push(URL.createObjectURL(image)));
setImageURLs(newImageUrls);
}, [images]);
function onImageChange(e: any) {
setImages([...e.target.files]);
}
return (
<>
<input type="file" multiple accept="image/*" onChange={onImageChange} />
{imageURLS.map((imageSrc) => (
<img src={imageSrc} alt="not fount" width={"250px"} />
))}
</>
);
};
export default UploadAndDisplayImage;

Handle Form
Initial data:
const initialProducts = {
name: "",
featured_image: "", //for single image
slider_images: [], // (array of strings)
};
useState
const [products, setProducts] = useState(initialProducts);
Function: Handle Input Fields
const handleInput = (e) => {
let updateValues = { ...products };
updateValues[e.target.name] = e.target.value;
setProducts(updateValues);
console.log("Update input values", updateValues);
};
Function: Handle Multiple Images
const handleSliderImages = (e) => {
if (e.target.files) {
setProducts({ ...products, slider_images: [...e.target.files] });
}
console.log("Update slider images", products);
};
FormGroup for Input Fields
<FormGroup className="my-3">
<Label for="name">Name</Label>
<Field
name="name"
id="name"
onChange={handleInput}
value={products.name}
/>
</FormGroup>
FormGroup for single Image
<FormGroup className="my-3">
<Label for="featured_image">Featured Image</Label>
<CustomInput
type="file"
id="featured_image"
name="featured_image"
onChange={handleInput}
value={products.featured_image}
/>
</FormGroup>
FormGroup for Multiple Images
<FormGroup className="my-3">
<Label for="slider_images">Slider Images</Label>
<CustomInput
multiple
type="file"
id="slider_images"
name="slider_images"
onChange={handleSliderImages}
/>
</FormGroup>

You can solve this problem by multiple input fields. You are able to track the input field used to upload the file using their unique "ID". Please take a look to the code that I shared.
Input field
onChange handler
file handler based on id

It is simple You should follow the steps which i wrote and i hope this will help

Related

Displaying an image of a file from input

I am trying to show an image of a file that was selected from input without sending data to the server.
Wanted to do it with URL.createObjectUrl.
const onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
this.setState({
image: URL.createObjectURL(event.target.files[0])
});
}
}
<input type="file"
onChange={onImageChange}
className="filetype"
id="group_image"/>
And then pass an image inside
<img
id="target"
src={this.state.image}/>
But i get undefined has no properties with this code. So does anyone know how to do it?
EDIT: https://codesandbox.io/s/restless-sun-61uui?file=/src/App.js
Full code:
import React from 'react'
const Addfile = () => {
const onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
this.setState({
image: URL.createObjectURL(event.target.files[0])
});
}
}
return (
<div>
<input type="file"
onChange={onImageChange}
className="filetype"
id="group_image"/>
<img
id="target"
src={this.state.image}/>
</div>
)
}
export default Addfile
You are using a functional component. There is not this.state, isn't it?
You should use useState. I updated the codesandbox for you: https://codesandbox.io/s/zealous-noether-3tz4f?file=/src/App.js

Cannot update state with file uploader in reactJS

I created a simple image uploader component that is rendered in a new-image-form that extends a form. but when handling a change(an image upload), the state is updated with an empty opbject (and not with the file object). I tried to debug it but anything goes right until this.setState. Can anyone helps?
NewImagesForm :
class NewImagesForm extends Form {
state = {
data: {
_id: 0,
name: "",
file: null,
},
errors: {},
apiEndpoint: "",
};
render() {
return (
<div className="new-menu-item-form">
<form onSubmit={this.handleSubmit}>
{this.renderInput("name", "Name")}
{this.renderUploadFile("file", "Upload")}
{this.renderButton("Add")}
</form>
</div>
);
}
Form
class Form extends Component {
state = { data: {}, errors: {} };
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};
renderUploadFile(name, label) {
const { data } = this.state;
return (
<UploadImage
name={name}
label={label}
value={data[name]}
onChange={this.handleFileUpload}
></UploadImage>
);
}
}
export default Form;
File Upload Component
const UploadImage = ({ name, label, error, onChange }) => {
return (
<div className="input-group">
<div className="custom-file">
<input
type="file"
onChange={onChange}
className="custom-file-input"
id={name}
name={name}
></input>
{label && (
<label className="custom-file-label" htmlFor={name}>
{label}
</label>
)}
</div>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button">
Button
</button>
</div>
</div>
);
};
export default UploadImage;
First of all, react does not recommend the use of extends, because of the combination of extends.
In the Form component, the setState() trigger the render() , but there is no render(). setState does not allow renderUploadFile to be re-executed.
It would be a good idea to use the UploadImage component in the NewImagesForm component. Don't let NewImagesForm extends Form.
But the exactly same way works for me with custom input text and custom select boxes, see the differences:
handleChange = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};

Saving JS State to an unordered list

class App extends Component {
constructor() {
super()
this.state = {
firstName: ""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
firstName: event.target.value
})
}
render() {
return (
<form>
<input type="text" placeholder="First Name" onChange={this.handleChange} />
<h1>{this.state.firstName}</h1>
</form>
);
}
}
export default App;
Hello all, I am currently studying React and seem to be having a hard time grasping all of it. The code that I have here works in that it will show in browser what the user is typing in the input box. What I cannot seem to figure out or get to work, is mapping what is typed in the input to stay on the screen. I.e. when I hit enter, it refreshes and the name goes away. I am trying to now create an unordered list to keep each name displayed on the screen. Any help or links would be greatly appreciated. Thank you
Just add new function (this describe what should be after submit this form) in this case You use:
event.preventDefault() -
The Event interface's preventDefault() method tells the user agent
that if the event does not get explicitly handled, its default action
should not be taken as it normally would be
onSubmit(event){
event.preventDefault()
}
and on form:
<form onSubmit={this.onSubmit}>
To create unordered list use something like this (credit for Robin Wieruch):
import React from 'react';
const initialList = [
'Learn React',
'Learn Firebase',
'Learn GraphQL',
];
const ListWithAddItem = () => {
const [value, setValue] = React.useState('');
const [list, setList] = React.useState(initialList);
const handleChange = event => {
setValue(event.target.value);
};
const handleSubmit = event => {
if (value) {
setList(list.concat(value));
}
setValue('');
event.preventDefault();
};
return (
<div>
<ul>
{list.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input type="text" value={value} onChange={handleChange} />
<button type="submit">Add Item</button>
</form>
</div>
);
};
export default ListWithAddItem;

React Form: Input Data Won't Display

Every time I click the button a post is being created and displayed
But the input data isn't showing up
import React, { Component } from 'react'
export default class App extends Component {
state = {
dataArr : [],
isClicked: false,
title : '',
img : ''
}
handleSubmit = (e) => {
e.preventDefault()
}
handleChange = (e) => {
[e.target.name] = e.target.value
}
handleClick = () => {
const copyDataArr = Object.assign([], this.state.dataArr)
copyDataArr.push({
title : this.state.title,
img : this.state.img
})
this.setState({
isClicked : true,
dataArr : copyDataArr
})
}
render() {
console.log(this.state.title)
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" name="title" placeholder="Title.." onChange={this.handleChange}/>
<input type="text" name="img" placeholder="Image.." onChange={this.handleChange}/>
<button onClick={this.handleClick}>SUBMIT</button>
</form>
{ this.state.isClicked ?
this.state.dataArr.map(
(post, index) => {
return(
<div class="div">
<h1>title: {post.title}</h1>
<img src={post.img} alt="ohno"/>
</div>
)
}
)
: null }
</div>
)
}
}
Created an empty array on state and copied the array and pushed in the input values. What am I doing wrong?
Need the onChange to work and the input data to display
Trying to learn forms in react. Hope someone can help out
Your handleChange function is not updating state with the data you enter to the inputs. Looks like you might have forgotten to call this.setState(). Try this
:
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
Also here is a sandbox with the working code: https://codesandbox.io/s/distracted-darkness-xp3nu
You have to add a value attribute to the input tag and pass the state value.
<input type="text" name="title" value={this.state.title} placeholder="Title.." onChange={this.handleChange}/>
Your handle change function should be as follows
handleChange = (e) => {
this.setState({[e.target.name] : e.target.value});
}

ReactJS: How to handle Image / File upload with Formik?

I am designing a profile page for my site using ReactJS.
Now my question is how do I upload the image from local machine and save it to the database and also displaying it in the profile page
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { AccountAction } from '../../actions/user/AccountPg1Action';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
class AccountInfo extends Component {
constructor(props) {
super(props)
this.state = {
currentStep: 1,
userAccountData: {
userid: '',
useravtar: '',
attachement_id: '',
}
}
}
handleFileUpload = (event) => {
this.setState({useravtar: event.currentTarget.files[0]})
};
handleChange = event => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
handleSubmit = event => {
let that = this;
const { AccountAction } = that.props;
event.preventDefault();
let accountInputs = {
userid: 49,
useravtar: that.state.image,
attachement_id: 478,
}
that.setState({
userAccountData: accountInputs,
})
AccountAction(accountInputs)
}
AccountInfoView = () => {
console.log(this.state.useravtar)
return (
<section id="account_sec" className="second_form">
<div className="container">
<React.Fragment>
<Formik
initialValues={‌{
file: null,
email: '',
phone: ''
}}
validationSchema={accountInfoSchema}
render={(values) => {
return(
<Form onSubmit={this.handleSubmit}>
<Step1
currentStep={this.state.currentStep}
handleChange={this.handleChange}
file= {this.state.useravtar}
handleFileUpload={this.handleFileUpload}
/>
</Form>
);
}}
/>
</React.Fragment>
)
}
render() {
return (
<div>{this.authView()}</div>
)
}
}
function Step1(props) {
console.log(props.useravtar)
if (props.currentStep !== 1) {
return null
}
return(
<div className="upload">
<label htmlFor="profile">
<div className="imgbox">
<img src="images/trans_116X116.png" alt="" />
<img src={props.useravtar} className="absoImg" alt="" />
</div>
</label>
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
<span className="guide_leb">Add your avatar</span>
</div>
)
}
When I do console in handleChange action for event.target.file[0] it responds with undefined.
Also, doing a console.log(this.state.useravtar) in handleSubmit action it shows a pathname like c:/fakepath/imgname.jpg
P.S: I have a multiple forms so I am using it in a Step wise. And i am using Redux Reducer for storing the data.
I have referred this link but my requirement is not looking like this.
Formik doesnot support fileupload by default, But you can try the following
<input id="file" name="file" type="file" onChange={(event) => {
setFieldValue("file", event.currentTarget.files[0]);
}} />
Here "file" represents the key that you are using for holding the file
And on submit you can get the filename, size etc for the file by using
onSubmit={(values) => {
console.log({
fileName: values.file.name,
type: values.file.type,
size: `${values.file.size} bytes`
})
If you want to set the file into components state then you can use
onChange={(event) => {
this.setState({"file": event.currentTarget.files[0]})};
}}
According to your code, you have to handle file upload as below
In AccountInfo add a function to handle file upload
handleFileUpload = (event) => {
this.setState({WAHTEVETKEYYOUNEED: event.currentTarget.files[0]})};
}
And pass the same function to Step1 Component as below
<Step1
currentStep={this.state.currentStep}
handleChange={this.handleChange}
file= {this.state.image}
handleFileUpload={this.handleFileUpload}
/>
In Step1 Component where you upload the file, Change the input as
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
If you need to preview the uploaded image then you can create a blob and pass the same as source for image as below
<img src={URL.createObjectURL(FILE_OBJECT)} />
EDIT-1
As URL.createObjectURL method is deprecated due to security issues, we need to use srcObject for Media Elements, to use that you can use ref to assign srcObject, for example
Assuming you are using class Components,
Constructor
in constructor you can use
constructor(props) {
super(props)
this.imageElRef = React.createRef(null)
}
HANDLE CHANGE FUNCTION
handleFileUpload = (event) => {
let reader = new FileReader();
let file = event.target.files[0];
reader.onloadend = () => {
this.setState({
file: reader.result
});
};
reader.readAsDataURL(file);
}
Element
<img src={this.state.file} />
Here is how I resolved it with Formik and Material UI
in your JS file, just declare a variable avatarPreview like below
const [avatarPreview, setAvatarPreview] = useState('/avatars/default.png');
<Box
display='flex'
textAlign='center'
justifyContent='center'
flexDirection='column'>
<ImageAvatar size='md' src={avatarPreview || user?.avatar} />
<Button
variant='contained'
component='label'
startIcon={<CloudUploadIcon />}>
Choose Avatar
<input
name='avatar'
accept='image/*'
id='contained-button-file'
type='file'
hidden
onChange={(e) => {
const fileReader = new FileReader();
fileReader.onload = () => {
if (fileReader.readyState === 2) {
setFieldValue('avatar', fileReader.result);
setAvatarPreview(fileReader.result);
}
};
fileReader.readAsDataURL(e.target.files[0]);
}}
/>
</Button>
</Box>
Default Preview:
After choosing avatar:
You can upload single or multiple files with validation using Formik as follows:
import "./App.css";
import { useEffect, useState } from "react";
import * as Yup from "yup";
import { Formik, Field, Form, ErrorMessage, useField } from "formik";
import axios from "axios";
function App() {
return (
<Formik
initialValues={{
profile: [],
}}
validationSchema={Yup.object({
profile:Yup.array().min(1,"select at least 1 file")
})}
onSubmit={(values, props) => {
let data = new FormData();
values.profile.forEach((photo, index) => {
data.append(`photo${index}`, values.profile[index]);
});
axios
.post("you_api_for_file_upload", data, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}}
>
{(formik) => {
return (
<>
<Form>
<input
id="file"
name="profile"
type="file"
onChange={(event) => {
const files = event.target.files;
let myFiles =Array.from(files);
formik.setFieldValue("profile", myFiles);
}}
multiple
/>
<ErrorMessage name="profile"/>
<button type="submit" disabled={formik.isSubmitting}>
Submit
</button>
</Form>
</>
);
}}
</Formik>
);
}
export default App;
Note: you can customize min(your choice, "your message") as per your need.
validationSchema={Yup.object({
profile:Yup.array().min(1,"select at least 1 file")
})}
FormIk does not support file uploading and we need to do it in custom way.
Just trigger onChange event and set the file.
If you face any error of setFieldValue in TypeScript, then you simply do this:
onChange={(event) => {
if (event.currentTarget.files) {
formik.setFieldValue(
"file",
event.currentTarget.files[0]
);
}
To handle file in formik with backend all you need to do add the input given below (you can change avatar to anything you want):
<input
type="file"
name="avatar"
onChange={(event) => {
setFieldValue('avatar', event.currentTarget.files[0]);
}}
/>
onSubmit you can access file using values obj like values.avatar.
On server-side (express) to access the file we use req.file
You also have to use multer that will automatically detect file to handle it on the server-side
To handle file in formik with backend all you need to do add the input given below (you can change avatar to anything you want):
<input
type="file"
name="avatar"
onChange={(event) => {
setFieldValue('avatar', event.currentTarget.files[0]);
}}
/>
onSubmit you can access file using values obj like values.avatar.
On server-side (express) to access the file we use req.file You also have to use multer & cloudinary that will detect file to handle it on the server-side
I used simple trick
<Field name="image">
{ (form , meta , value ) =>
const {setFieldValue} = form
return (
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.file[0])}
)
}
</Field>
for multiple image
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.files)}
or use loop
for( i=0 ; i < e.target.file; i++){
setFieldValue([...image , e.target.file])
}

Resources