Make a POST form-data with React to upload an image - reactjs

I am trying to upload an image within my ReactJS service to my NestJS API service, through my API, but it's not working yet. This is the React code:
First the form:
<div>
<input type="file" name="urlpromo" value={urlpromo} onChange={this.changeHandler} />
</div>
<button type="submit">Submit</button>
and the functions:
changeHandler = (e) => {
this.setState({[e.target.name]: e.target.value})
}
submitBaner = (e) => {
var bodyFormData = new FormData();
bodyFormData.append('file', this.state.urlpromo);
let config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
}
}
e.preventDefault()
console.log(bodyFormData)
axios.post('http://localhost:3000/images/upload', bodyFormData,config)
}
The thing is that before I was sending images, only with JSON body, it was working fine, but now with form-data, I cant make it work. This is how I can upload an image with Postman:
When I try to make it work, the function console log prints this:
FormData {}__proto__: FormData
What I am doing wrong, how should I work with this form-data?

As per the docs, <input type="file"> is uncontrolled due to its read-only value.
One option is to use a ref to track the <input> element and the files property to access the File
// in your constructor
this.urlPromoRef = React.createRef()
<div>
<input type="file" ref={this.urlPromoRef} />
</div>
<button type="submit">Submit</button>
and in your submit handler
e.preventDefault()
const bodyFormData = new FormData();
bodyFormData.append('file', this.urlPromoRef.files[0]);
// no need for extra headers
axios.post('http://localhost:3000/images/upload', bodyFormData)
Another option is to simply pass the <form> itself into the FormData constructor.
<form onSubmit={this.submitBaner}>
<div>
<input type="file" name="urlpromo" /> <!-- must have a name -->
</div>
<button type="submit">Submit</button>
</form>
submitBaner = (e) => {
e.preventDefault()
const bodyFormData = new FormData(e.target); // pass in the form
axios.post('http://localhost:3000/images/upload', bodyFormData)
}
Finally, you may be able to use something like your original code but with a special check for <input type="file">. Eg
changeHandler = (e) => {
const el = e.target
this.setState({
[el.name]: el.type === "file" ? el.files[0] : el.value
})
}

Related

ReactJs form not displaying in browser even if the code runs with no erros

I wrote this code for a form to collect images and text data, it runs fine with no errors but in the browser, nothing is displayed but a blank screen.
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useDropzone } from 'react-dropzone';
const Form = () => {
const { register, handleSubmit } = useForm();
const [images, setImages] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: 'image/*',
onDrop: acceptedImages => {
setImages(acceptedImages.map(image => Object.assign(image, {
preview: URL.createObjectURL(image)
})));
}
});
const onSubmit = async data => {
const formData = new FormData();
images.forEach(image => {
formData.append('images', image);
});
formData.append('name', data.name);
formData.append('description', data.description);
try {
const response = await fetch('http://localhost:8000/submit-form', {
method: 'POST',
body: formData
});
console.log(response);
} catch (err) {
console.error(err);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<br />
{images.map(image => (
<img key={image.name} src={image.preview} alt={image.name} style={{ width: '200px' }} />
))}
<br />
<input name="name" ref={register} placeholder="Name" />
<br />
<textarea name="description" ref={register} placeholder="Description" />
<br />
<button type="submit">Submit</button>
</form>
);
}
export default Form
I expected to see a form in the browser and at least see if it actually works but i saw none. I'm using react Dropzone and react hook form on the form. And maybe a fetch for the data.
Try to change the ref in input and textarea tag like so:
<input name="name" {...register('name')} placeholder="Name" />
<textarea name="description" {...register('description')} placeholder="Description" />
Reference: https://react-hook-form.com/get-started/
I suspect you haven't called the e.preventDefault() to prevent the default form submission. So, you may try the following:
Replace the statement:
<form onSubmit={handleSubmit(onSubmit)}>
to
<form onSubmit={handleSubmit}>
change the handleSubmit function to:
const onSubmit = e => {
e.preventDefault(); //prevent submit form
let form = e.target; //get the form obj
const formData = new FormData();
images.forEach(image => {
formData.append('images', image);
});
formData.append('name', form.name.value);
formData.append('description', form.description.value);
..............................
}

How to get progressbar when file upload to browser

How to get file uploading percentage when file uploading from client file system to client's browser.
I have the simplest form. I need to get file uploading percentage and show it to user when he upload file.
export default function App() {
const handleSubmit = (e) => {
e.preventDefault();
console.log("submit");
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input type="file" />
<input type="submit" value="click to submit" />
</form>
</div>
);
}
Demo
here is small example, with react bootsttrap you can try,
you have to import,
import { Container, Row, Col, Form, Button, ProgressBar } from "react-bootstrap"
your javascript logic will be like this, ProgressBar needs % percentage value, you can calculate that,
const [selectedFiles, setSelectedFiles] = useState()
const [progress, setProgress] = useState()
const handleSubmit = e => {
e.preventDefault() //prevent the form from submitting
let formData = new FormData()
formData.append("file", selectedFiles[0])
const axios = axios.create({
baseURL: "http://localhost:8080/",
})
axios.post("/upload_file", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: data => {
//Set the progress value to show the progress bar
setProgress(Math.round((100 * data.loaded) / data.total))
},
})
}
here is component return will look like,
<Form
action="http://localhost:8081/upload_file"
method="post"
encType="multipart/form-data"
onSubmit={handleSubmit}
>
<Form.Group>
<Form.File
label="Select a File"
onChange={e => {
setSelectedFiles(e.target.files)
}}
/>
</Form.Group>
<Form.Group>
<Button variant="info" type="submit">
Upload A File
</Button>
</Form.Group>
{progress && <ProgressBar now={progress} label={`${progress}%`} />}
</Form>

Why is the preventDefault() method not working for me in my react application?

I used a form to capture input from user. But once the user clicks on the submit button, the page reloads. I don't want it to reload. That was why I decided to use preventDefault() method.
How can I get this to work?
const handleHeaderValueUpdate = async ( headerId, e) =>{
e.preventDefault()
console.log(headerId)
setUpdating(true)
const updatedHeaderValue = {
username: user.username,
websiteName,
sitesubName,
};
if(file){ //logic behind uploading image and the image name
const data = new FormData();
const filename = Date.now() + file.name;
data.append("name", filename);
data.append("file", file);
updatedHeaderValue.headerImg = filename;
try{
await axios.post("/upload", data)//handles the uploading of the image
//setUpdated(true)
}catch(err){
dispatch({type: "UPDATE_FAILURE"})
}
}
try{
await axios.put("/headervalue/" + headerId, updatedHeaderValue)
setUpdating(false);
setTextUpdate(true)
//setUpdated(true)
}catch(err){
console.log(err)
}
}
Here is part of the form:
<form onSubmit={()=>handleHeaderValueUpdate(headerId)} className="dashboard-form">
<div className='dasboard-input-div'>
<p className="dashboard-label">Website Name</p>
{dashboardEditMode ? <input className="dashboard-input" type="text" placeholder={singleValues.websiteName} autoFocus
onChange={(e) => setWebsiteName(e.target.value)}
required
/> :
<input className="dashboard-input" type="text" placeholder= {singleValues.websiteName} readOnly/>
}
</form>
Here is the submit button
{dashboardEditMode && <button className={updating ? "updateModeBTN-unclick updateModeBTN" : "updateModeBTN"} type="submit">Update</button>}
The problem is you do not send the event to the handleHeaderValueUpdate function. You should change:
<form onSubmit={()=>handleHeaderValueUpdate(headerId)}
to:
<form onSubmit={(e) => handleHeaderValueUpdate(headerId, e)}
You can take a look at this sandbox for a live working example.

preventDefault() not working on submit in React

I'm currently working in react and have a couple of forms where the onSubmit functions automatically refresh the page even if I use preventDefault(). Im passing the event into the functions as well. Could really use some guidance on why these two forms are having this issue. It hasn't been a problem elsewhere.
Here's the form. Verify is automatically passing e.
<form onSubmit={verify} className='username-password-form'>
<div className='old-password-container'>
<label className='old-password-label'>Current Password:</label>
<input
className='old-password-input'
type='password'
id={`${id}-old-password`}
value={currentPassword}
name='currentPassword'
disabled={disabled}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
<Button className='submit-old' type='submit'>
Submit
</Button>
</div>
</form>
Here's the verify function called onSubmit
const verify = async (e) => {
e.preventDefault();
const user = {
username: user_name,
password: currentPassword,
};
await fetch('http://localhost:3000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
});
setDisabled(true);
};

Issue to submit CSV file to DB via POST Axios & React-Final-Form

I need to save the content of a CSV into a database table by using React-Final-Form and Axios.
I have tried to create a simple HTML without using Final-Form or Axios and the submission to the DB works fine.
The problem is when I try to pass the content of the CSV to a function which will handle the POST call.
See code below:
import React, { Fragment } from "react";
import { Form, Field } from "react-final-form";
import createDecorators from "final-form-focus";
const handleSubmitOnClick = file => {
const url = 'http://localhost:3000/api/v1/invitations/upload';
const data = new FormData();
data.append('file', new File([file], { type: 'text/csv' }));
return axios.post(url, data, {
headers: {
'content-type': 'multipart/form-data'
}
})
.then(response => console.log(response))
.catch(error => console.log(error));
}
const JoinTesting = () =>
<Fragment>
<h1>Join Testing Page</h1>
<Form
onSubmit={handleSubmitOnClick}
decorators={[focusOnError]}
>
{
({
handleSubmit,
values,
submitting,
}) => (
<form onSubmit={handleSubmit} encType="multipart/form-data">
<Field
name='invitation[file]'
placeholder='Upload csv file'
validate={required}
>
{({ input, meta, placeholder }) => (
<div className={meta.active ? 'active' : ''}>
<label>{placeholder}</label>
<input {...input}
type='file'
placeholder={placeholder}
className="join-field-input"
/>
{meta.error && meta.touched && <span className="invalid">{meta.error}</span>}
{meta.valid && meta.dirty && <span className="valid">Great!</span>}
</div>
)}
</Field>
<button
type="submit"
className="join-button"
disabled={submitting}
>
Submit
</button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
</Form>
</Fragment>
export default JoinTesting;
If I remove ALL the above and I just use this HTML within my JoinTesting component, it works fine but I can't handle the errors (if any)
<form action="http://localhost:3000/api/v1/invitations/upload" method="post" encType="multipart/form-data">
Select CSV to upload:
<input type="file" name="invitation[file]" id="fileToUpload" />
<br></br>
<input type="submit" value="Upload CSV" name="submit" />
</form>
PLEASE NOTE: The CSV file has only a simple 1 column with a sequence of email addresses.
This is what the POST request expects:
Headers:
Content-Type: application/json
Accept: application/json
Body
{
"invitation": {
"file": "Email\nuser_1#gmail.com\nuser_2#gmail.com\nuser_3#gmail.com\nuser_4#gmail.com\n"
}
}
The API response expected for a success call is:
{
"success": true,
"emails": [
"user_1#gmail.com",
"user_2#gmail.com",
"user_3#gmail.com",
"user_4#gmail.com"
]
}
I hope someone can help.
George
If you're not using html form + HTTP POST + encType="multipart/form-data", then you'll need to handle the file upload yourself.
One way to do it: get reference to the input component, listen to changes, when a change happens get the filename from the input reference, read the file, save the data. Here's a component for that:
function FileInput(props) {
const fileInput = useRef(null);
const setRef = ref => {
fileInput.current = ref;
};
async function handleInputChange() {
const { files } = fileInput.current;
props.onChange(files[0]);
}
return (
<input
ref={setRef}
type="file"
placeholder={props.placeholder}
className="join-field-input"
onChange={handleInputChange}
/>
);
}
Use this within the Field component, and the form state will contain the file:
<Field name="invitation[file]" placeholder="Upload csv file">
{({ input, meta, placeholder }) => (
// ...
<FileInput {...input} placeholder={placeholder} />
// ...
)}
</Field>
Also the handleSubmitOnClick gets the whole values object. Your values should look something like this:
values = { invitation: { file: {} } }
So, change the handleSubmitOnClick function to:
const handleSubmitOnClick = values => {
const data = new FormData();
const { file } = values.invitation;
data.append('file', file, file.name);
// ...
}
Here's a codesandbox.

Resources