'Upload a file in formik multistep
I have implemented formik multi-step form using formik-wizard-form. It works fine with other inputs except for file upload.
I am using the following code for file upload:
<Form.Group controlId="upload">
<Col>
<div className="form-group files">
Add Attachments:
<input
type="file"
name="upload"
value={values.upload?.files }
onChange={event => {
setFieldValue("upload", event.currentTarget.files);
}}
multiple />
</div>
</Col>
</Form.Group>
Console log upload values is as shown below.
How do I pass the file uploads to the server?
Solution 1:[1]
The trick with file inputs according to docs is:
In React, an
<input type="file" />
is always an uncontrolled component because its value can only be set by a user, and not programmatically.
So it uses it's own mechanism along with browser's security implementations to set value of the sent file. For security reasons, you can't get the full file path on the local computer of the one sent it, although we can use a FileReader object to asynchronously read the contents of file and then use the result to fill the value and send it to the parent form component.
There are cases where you can change file input's value programmatically, like to null to reset your input.
In my multi step form I have used formik and material-ui. it has a field (UploadFiled) with type="file"
get wrapped with a custom Field component.
Whenever child component getting update, a file is being uploaded, and parent component will knows and starts reading the contents of the specified Blob, once finished, the result attribute contains a data: URL representing the file's data will set as it's value. It currently accepts an image base on the validation that is set for it using Yup.
The traditional application/json type won’t help in uploading the image to your server, the FormData will. You need to write your handleSubmit() using the form data and pass the values handled by Formik.
You can use any from the Fetch API or the Axios for sending the POST request to your serve, depends on you preferences.
const onSubmit = () => {
// Create an object of formData
const formData = new FormData();
// Update the formData object
formData.append("myFile", file, file.name);
// Details of the uploaded file
console.log(file);
// Request made to the backend api
// Send formData object
axios.post("api/uploadfile", formData);
};
// UploadForm.jsx
import React, { useState, useEffect } from "react";
import { Field, useField } from "formik";
import { Grid, FormHelperText } from "@material-ui/core";
import UploadField from "../../FormFields/UploadField";
import Thumb from "../Helper/Thumb";
const ImageForm = (props) => {
const {
formField: { image }
} = props;
const [field, meta, helper] = useField(image.name);
const { touched, error } = meta;
const { setValue } = helper;
const isError = touched && error && true;
const { value } = field;
const [fileName, setFileName] = useState(value.name);
const [file, setFile] = useState(value.file);
const [src, setSrc] = useState(value.src);
const _onChange = (e) => {
let reader = new FileReader();
let file = e.target.files[0];
if (file) {
reader.onloadend = () => setFileName(file.name);
if (file.name !== fileName) {
reader.readAsDataURL(file);
setSrc(reader);
setFile(file);
}
}
};
useEffect(() => {
if (file && fileName && src) {
setValue({ file: file, src: src, name: fileName });
console.log(fileName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [src, fileName, file]);
return (
<React.Fragment>
<Grid container spacing={3} justify="center" alignItems="center">
<Grid item xs={12}>
<label>
{image.label}
</label>
<br />
<div
style={{
display: "flex",
justifyContent: "flex-start",
fontSize: "1.2em"
}}
>
<Field
variant="outlined"
field={field}
component={UploadField}
onChange={_onChange}
isError={isError}
/>
{isError && <FormHelperText color={"red"}>{error}</FormHelperText>}
</div>
</Grid>
<Grid item>{file && src && <Thumb file={file} src={src}></Thumb>}</Grid>
</Grid>
</React.Fragment>
);
};
export default ImageForm;
// UploadField.jsx
import React from "react";
import { Field } from "formik";
const UploadField = ({
field,
form: { touched, errors },
name,
label,
isError,
...props
}) => {
return (
<>
<Field
variant="outlined"
name="uploader"
title={label}
type={"file"}
{...props}
/>
</>
);
};
export default UploadField;
Link to Code Sandbox,
Solution 2:[2]
You can use setFieldValue from formik. ref : Formik
const _onChange = (e) => {
let reader = new FileReader();
let file = e.target.files[0];
if (file) {
reader.onloadend = () => setFileName(file.name);
if (file.name !== fileName) {
reader.readAsDataURL(file);
setSrc(reader);
setFieldValue('file',file);
}
}
};
Solution 3:[3]
Formik does not support file upload 'out-of-the-box'. So if you upload files asynchronously you will have to do something like this:
const rebuildData = (values) => {
let formData = new FormData();
Object.keys(values).forEach(key => {
formData.append(key, values[key]);
});
return formData;
};
<Formik
onSubmit={values => {
const data = rebuildData(values);
axios({
method: 'post',
url: '/YOUR_ENDPOINT',
data
})
}
}
Do not forget to add the rest of your form data to formData
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Shahnad |
Solution 3 | Hector |