0
votes

I am trying to do a file upload but the whole component refreshes every time. I have 3 steps here, 1st one takes name, 2nd takes address and the third one takes profile pic. In third step when the user clicks on the button to select file from file system the whole stepper components gets reloaded.

Is there a different way I can use file upload? I am not sure if I can put things together with the formik values.

export default function Test() {
  const showFileToUpload = (e) => {
    console.log("files", e);
    //then perform some requests to save these images
  };

  return (
    <FormikStepper
      initialValues={{
        firstName: "",
        address: "",
        file: ""
      }}
      onSubmit={async (values) => {
        console.log("values", values);
      }}
    >
      <FormikStep label="Basic Details">
        <FormControl>
          <Field
            type="text"
            name="firstName"
            label="Name"
            component={TextField}
            autoComplete="off"
          />
        </FormControl>
      </FormikStep>
      <FormikStep label="Other details">
        <FormControl>
          <Field
            type="text"
            name="address"
            label="address"
            component={TextField}
            autoComplete="off"
          />
        </FormControl>
      </FormikStep>
      <FormikStep
        label="Upload Files"
        showFileToUpload={(w) => {
          console.log(w);
        }}
      >
        <div className="form-group">
          <label>Upload your images</label>
          <input
            id="file"
            name="file"
            type="file"
            multiple={true}
            onChange={(e) => {
              showFileToUpload(e.currentTarget.files);
            }}
          />
          {/* <Thumb file={values.file} /> */}
        </div>
      </FormikStep>
    </FormikStepper>
  );
}

export function FormikStep({ children, ...props }) {
  console.log("valuessss", props);

  return (
    <Card style={{ width: "100%", marginBottom: "20px", padding: "10px" }}>
      <CardContent
        className="cardContentCustom"
        style={{
          width: "100%",
          marginBottom: "20px",
          display: "flex",
          flexWrap: "wrap"
        }}
      >
        {children}
      </CardContent>
    </Card>
  );
}

export function FormikStepper({ children, ...props }) {
  const childrenArray = React.Children.toArray(children);
  const [step, setStep] = useState(0);
  const currentChild = childrenArray[step];
  const [completed, setCompleted] = useState(false);
  console.log("values", props, childrenArray);

  function isLastStep() {
    return step === childrenArray.length - 1;
  }

  return (
    <Formik
      {...props}
      validationSchema={currentChild.props.validationSchema}
      onSubmit={async (values, helpers) => {
        console.log("--------", props);
        if (isLastStep()) {
          await props.onSubmit(values, helpers);
          setCompleted(true);
        } else {
          setStep((s) => s + 1);
        }
      }}
    >
      {({ isSubmitting }) => (
        <Form autoComplete="off">
          <Card
            style={{
              width: "100%",
              marginBottom: "20px",
              height: "fit-content"
            }}
          >
            <CardContent style={{ width: "100%" }}>
              <Stepper alternativeLabel activeStep={step}>
                {childrenArray.map((child, index) => (
                  <Step
                    key={child.props.label}
                    completed={step > index || completed}
                  >
                    <StepLabel>{child.props.label}</StepLabel>
                  </Step>
                ))}
              </Stepper>
            </CardContent>
          </Card>

          {currentChild}

          <Grid container spacing={2}>
            {step > 0 ? (
              <Grid item>
                <Button
                  disabled={isSubmitting}
                  variant="contained"
                  color="primary"
                  onClick={() => setStep((s) => s - 1)}
                >
                  Back
                </Button>
              </Grid>
            ) : null}
            <Grid item>
              <Button
                startIcon={
                  isSubmitting ? <CircularProgress size="1rem" /> : null
                }
                disabled={isSubmitting}
                variant="contained"
                color="primary"
                type="submit"
              >
                {isSubmitting ? "Submitting" : isLastStep() ? "Submit" : "Next"}
              </Button>
            </Grid>
          </Grid>
        </Form>
      )}
    </Formik>
  );
}

Here is the link for codesandbox: https://codesandbox.io/s/suspicious-hooks-g0wpl?file=/src/Test.js

1

1 Answers

0
votes

I remember doing ot with Antdesign few months ago. The antdesign component wasn't suitable to integrate just like this in the formik form. So i had to kind of wrap it in the FormItem from formik to make it work

Basically you pass create a Formik compatible field using FormItem. Then in it you pass your material ui component and spreads the props of formik to it.

You might have to refactor the onInputChange/ onChange method to suit the materialUi component method or whatever it needs for the file changer.

import React from "react";
import { Input } from "@material-ui/core";
import InputFile from "./InputFile";

const CreateFields = (CustomComponent) => ({
  field,

  hasFeedback,
  label,
  selectOptions,
  submitCount,
  type,
  ...props
}) => {
  //this will now trigger an error
  const touched = form.touched[field.name];
 //this will now trigger an error
  const submitted = submitCount > 0;
 //this will now trigger an error
  const hasError = form.errors[field.name];
  const submittedError = hasError && submitted;
  const touchedError = hasError && touched;
  const onInputChange = ({ target: { value } }) =>
    form.setFieldValue(field.name, value);
  const onChange = (value) => form.setFieldValue(field.name, value);
  const onBlur = () => form.setFieldTouched(field.name, true);
  return (
    <div className="field-container">
      <Input
        label={label}
        hasFeedback={
          (hasFeedback && submitted) || (hasFeedback && touched) ? true : false
        }
        help={submittedError || touchedError ? hasError : false}
        validateStatus={submittedError || touchedError ? "error" : "success"}
      >
        <CustomComponent
          {...field}
          {...props}
          onBlur={onBlur}
          onChange={type ? onInputChange : onChange}
        ></CustomComponent>
      </Input>
    </div>
  );
};

export const MyCustomFormikAndMaterialUiField = CreateFields(InputFile);

Where you see the //this will noiw trigger an error, it's beceause i don't know for Material ui how the props are handleded for the Input.

You need to check the documentation of material ui an try to adapt with the props for the Input, when it hasError, it's touched, or whatever this Input field has.

Im sure my answer won't work now, but it is the good way to follow i think to make it work.

Let me know if you need more clarification