How to get progressbar when file upload to browser - reactjs

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>

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

API Calls with Axios fail on React

On handleSubmit i am trying to log in using an API hosted on Heroku and made based on ExpressJS. It is working fine when i am using Postman to reach the API on the login endpoint. But when using axios on react it fails.
Heroku login endpoint : https://nodejs-mongodb-authapp.herokuapp.com/login
Code :
import React, { useState } from "react";
import './Login.css' ;
import {FaMountain} from 'react-icons/fa';
import { Form, Button } from "react-bootstrap";
import axios from "axios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [login, setLogin] = useState(false);
const [state , setState] = useState(false);
const axios = require("axios");
const handleSubmit = (event) =>{
const data = JSON.stringify({
email: "au#outlook.com",
password: "zzz",
});
const config = {
method: "post",
url: "https://nodejs-mongodb-authapp.herokuapp.com/login",
headers: {
"Content-Type": "application/json",
},
data: data,
};
axios(config)
.then((result) => {
alert("in thenn");
// setLogin(true);
// cookies.set("TOKEN", result.data.token, {
// path: "/",
// });
// navigate('/auth');
})
.catch((error) => {
error = new Error();
});
}
return(
<div className="p1">
<div className="log">
<form className="formFields" >
<div className="lfa">
<FaMountain />
</div>
<p>LOG IN</p>
<Form onSubmit={(e)=>handleSubmit(e)} className="form_">
{/* email */}
<Form.Group controlId="formBasicEmail">
<Form.Control
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
/>
</Form.Group>
{/* password */}
<Form.Group controlId="formBasicPassword">
<Form.Control
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
</Form.Group>
{/* submit button */}
<Button
variant="primary"
type="submit"
onClick={(e) => handleSubmit(e)}
>
Login
</Button>
</Form>
</form>
</div>
{/* {login ? (
<p className="text-success">You Are Logged in Successfully</p>
) : (
<p className="text-danger">You Are Not Logged in</p>
)} */}
</div>
)
}
The API is public for the moment you can try login with those credentials on PostMan :
Well, I managed to test it and it does respond. The request should be done like this:
import React, { useState } from "react";
import { FaMountain } from "react-icons/fa";
import { Form, Button } from "react-bootstrap";
import Cookies from "universal-cookie";
const cookies = new Cookies();
export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [login, setLogin] = useState(false);
const axios = require("axios");
const handleSubmit = (event) => {
event.preventDefault();
const data = JSON.stringify({
email: "au#outlook.com",
password: "zzz"
});
const config = {
method: "post",
url: "https://nodejs-mongodb-authapp.herokuapp.com/login",
headers: {
"Content-Type": "application/json"
},
data: data
};
axios(config)
.then((result) => {
alert("in thenn");
console.log(result);
setLogin(true);
cookies.set("TOKEN", result.data.token, {
path: "/"
});
})
.catch((error) => {
error = new Error();
});
};
return (
<div className="p1">
<div className="log">
<div className="lfa">
<FaMountain />
</div>
<p>LOG IN (Login State {login.toString()})</p>
<p>Token: {cookies.get('TOKEN')}</p>
<Form onSubmit={(e) => handleSubmit(e)} className="form_">
{/* email */}
<Form.Group controlId="formBasicEmail">
<Form.Control
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
/>
</Form.Group>
{/* password */}
<Form.Group controlId="formBasicPassword">
<Form.Control
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
</Form.Group>
{/* submit button */}
<Button
variant="primary"
type="submit"
onClick={(e) => handleSubmit(e)}
>
Login
</Button>
</Form>
</div>
{/* {login ? (
<p className="text-success">You Are Logged in Successfully</p>
) : (
<p className="text-danger">You Are Not Logged in</p>
)} */}
</div>
);
};
Since you are sending a JSON with the access information through the body, you have to take the JSON structure and then chain it to send it through post, and you have to add the ContentType: application/json header.
UP: When forms are used and you use an input type submit, every time the login button is clicked, the component is updated, to avoid this add the following inside the function event.preventDefault();
Test it here

How to add field to upload image by Redux Form?

I want to make a submit form by Redux-Form which has a image upload field along with other text fields. I have tried the following approach for image upload and the problem is whenever I try to upload image the form gets re-rendered. How can I do it in a proper way? And another thing is How can I send entire form data (including uploaded image) to Back end? I have used here react,redux-form and material-ui
<Box className={classes.controlTitle}>
Upload Organization Logo
</Box>
<Field
name="logo"
type="file"
component={renderField}
placeholder="Upload your organization logo"
className={classes.field}
/>
I suggest using something like react-uploady. It takes care of the file upload for you and you can use any form/components/ui libraries with it:
import React, { useState, useCallback, useMemo, forwardRef } from "react";
import styled, { css } from "styled-components";
import Uploady, {
useBatchAddListener,
useBatchFinishListener,
useUploadyContext
} from "#rpldy/uploady";
import { asUploadButton } from "#rpldy/upload-button";
const MyUploadField = asUploadButton(
forwardRef(({ onChange, ...props }, ref) => {
const [text, setText] = useState("Select file");
useBatchAddListener((batch) => {
setText(batch.items[0].file.name);
onChange(batch.items[0].file.name);
});
useBatchFinishListener(() => {
setText("Select file");
onChange(null);
});
return (
<div {...props} ref={ref} id="form-upload-button" title={text}>
{text}
</div>
);
})
);
const MyForm = () => {
const [fields, setFields] = useState({});
const [fileName, setFileName] = useState(null);
const uploadyContext = useUploadyContext();
const onSubmit = useCallback(() => {
uploadyContext.processPending({ params: fields });
}, [fields, uploadyContext]);
const onFieldChange = useCallback(
(e) => {
setFields({
...fields,
[e.currentTarget.id]: e.currentTarget.value
});
},
[fields, setFields]
);
const buttonExtraProps = useMemo(
() => ({
onChange: setFileName
}),
[setFileName]
);
return (
<Form>
<MyUploadField autoUpload={false} extraProps={buttonExtraProps} />
<br />
<input
onChange={onFieldChange}
id="field-name"
type="text"
placeholder="your name"
/>
<br />
<input
onChange={onFieldChange}
id="field-age"
type="number"
placeholder="your age"
/>
<br />
<button>
id="form-submit"
type="button"
onClick={onSubmit}
disabled={!fileName}
>
Submit Form
</button>
</Form>
);
};
export default function App() {
return (
<div className="App">
<Uploady
clearPendingOnAdd
destination={{ url: "[upload-url]" }}
multiple={false}
>
<MyForm />
</Uploady>
</div>
);
}
You can check out this sandbox for a complete example.

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.

Upload file using Dropzone in react.js component to backend in sails.js

The backend of my app uses sails.js, and there's an API api/question/new for creating a new question. This API supports uploading a file or a picture by using its skipper module here, which I've verified working in the REST client PostMan using a file field for upload along with text fields for title, content, and tags.
Now, I am trying to make my react.js-based component (Question Form) work for posting text and uploading file at the same time. I came across a nice library called react-dropzone here, and I am trying to integrate it with my form. The form in react.js is handled by a saga function for posting with the whatwg-fetch library.
Question Form Component:
export const fields = ['title', 'content', 'tags', 'images'];
const validate = values => {
const errors = {};
if (!values.title) {
errors.title = 'Required';
}
if (!values.content) {
errors.content = 'Required';
} else if (values.content.length < 2) { // TODO: specify a constant for length of content
errors.content = 'Question is too short';
}
return errors;
}
class QuestionForm extends React.Component {
constructor(props, context) {
super(props, context);
this.dropFile = this.onDrop.bind(this);
}
onDrop(files) {
console.log('received files: ', files);
this.props.fields.images = [];
files.forEach((file) => {
this.props.fields.images.push(file);
});
}
render() {
const { fields: {title, content, tags, images}, resetForm, handleSubmit, submitting, pristine} = this.props;
return (
<div className={ styles.QuestionForm }>
<form onSubmit={handleSubmit}>
<Row>
<label>Title</label>
<div>
<input className="form-control" type="text" maxlength="100" placeholder="Title" {...title} name="title" />
</div>
{title.touched && title.error && <div>{title.error}</div>}
</Row>
<Row>
<label>Content</label>
<div>
<input className="form-control" type="text" maxlength="100" placeholder="Content" {...content} name="content" />
</div>
{content.touched && content.error && <div>{content.error}</div>}
</Row>
<Row>
<label>Tags</label>
<div>
<input className="form-control" type="text" maxlength="100" placeholder="Tags" {...tags} name="content" />
</div>
</Row>
<Row>
<Dropzone onDrop={this.dropFile}>
<div>Try dropping some files here, or click to select files to upload.</div>
</Dropzone>
</Row>
<Row>
<label></label>
<button className="btn btn-warning" type="button" disabled={pristine || submitting} onClick={resetForm}>Clear Values</button>
<button className="btn btn-primary" type="submit" disabled={pristine || submitting}>Submit</button>
</Row>
</form>
</div>
);
}
}
Below is the saga function for posting the form data with whatwg-fetch:
export function* askQuestion() {
while(true) {
const action = yield take(types.SUBMIT_QUESTION);
let askQUrl = 'http://localhost:1337/api/question/new';
const curToken = getJwtToken();
var formData = new FormData();
action.questionObj.images.forEach(function(img) {
formData.append('images', img);
});
formData.append('title', action.questionObj.title);
formData.append('content', action.questionObj.content);
formData.append('tags', action.questionObj.tags);
console.info('formData: ', formData); // here the images field shows as undefined
try {
let options = {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data'
},
// body: JSON.stringify(action.questionObj),
body: formData,
};
const response = yield call(request, askQUrl, options);
if (response.data) {
yield put(submitQuestionSuccess(response.data));
} else {
yield put(submitQuestionFailed(response.error));
}
} catch(e) {
yield put(submitQuestionFailed('server problem in taking your question'));
}
}
}
When I submit the form with the title/content/tags and a file, I got the following errors in the browser console:
uncaught at askQuestion TypeError: Cannot read property 'forEach' of
undefined
I am not sure why the action.questionObj.images is undefined, because in the dropFile() function, I push the file into the images array. Could anyone spot where I am doing wrong? or is there a better way to post both text and file same time to sails.js-based backend?

Resources