Denn
Denn

Reputation: 487

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?

console log image values

Upvotes: 3

Views: 12301

Answers (3)

Nima Bastani
Nima Bastani

Reputation: 305

The approach with file inputs, as outlined in the Documentation, 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.

Thus, it utilizes its own mechanism along with the browser's security implementations to set the value of the sent file. For security reasons, you can't retrieve the full file path on the local computer of the sender. However, you can use a FileReader object to asynchronously read the contents of the 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. A field (UploadField) with type="file" is wrapped with a custom Field component. Whenever the child component updates due to a file being uploaded, the parent component is notified and starts reading the contents of the specified Blob. Once finished, the result attribute contains a data URL representing the file's data, which is then set as its value. Currently, it accepts an image based on the validation that is set for it using Yup.

The traditional application/json type won’t suffice for uploading the image to your server; you'll need to use FormData instead. You need to write your handleSubmit() function using the form data and pass the values handled by Formik.

You can use either the Fetch API or Axios to send the POST request to your server, depending on your 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,

Upvotes: 3

lazy.lizard
lazy.lizard

Reputation: 919

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.

Upvotes: 1

Shahnad S
Shahnad S

Reputation: 1167

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

Upvotes: 0

Related Questions