1

I'm using a custom hook called useMultiStepForm to manage a multi-step form in my React application. The hook provides the current step index (currentStepIndex) among other functionalities. I need to access the currentStepIndex value outside of the useMultiStepForm hook, specifically in the validationSchema configuration of formik. However, when I try to pass currentStepIndex to another function or variable, I encounter a reference error: "Uncaught ReferenceError: can't access lexical declaration 'currentStepIndex' before initialization."

I'm able to use other values from the useMultiStepForm hook, such as isLastStep, without any error. But accessing currentStepIndex seems to be causing the issue.

Is there a way to access the currentStepIndex value outside of the useMultiStepForm hook without encountering the reference error? I'm looking for a solution or workaround that allows me to pass the currentStepIndex value to other parts of my code, specifically to the validationSchema configuration of formik.

Any help or suggestions would be greatly appreciated. Thank you!

here is the code

const NewRecord = () => {
  const formik = useFormik({
    initialValues: {
      houseHolderName: "",
      houseHolderNo: "",
      email: "",
      address: "",
      phone: "",
      numberOfFamilyMembers: "2",
      familyMembersDetails: [],
      waterAvialablity: true,
      powerAvailability: true,
      owenership: true,
    },
    validationSchema: recordSchema(currentStepIndex), // here I want to pass current step index but I am getting error -  Uncaught ReferenceError: can't access lexical declaration 'currentStepIndex' before initialization

    onSubmit: (data) => {
      if (!isLastStep) return next(); // in here I am not getting any error even though it is also comming from useMultiStepForm hook

      // make api call here  // data oject contains all of the data
    },
  });

  const handleAddFamilyMember = () => {
    formik.setFieldValue(
      "familyMembersDetails",
      [
        ...Array(parseInt(formik.values.numberOfFamilyMembers)).fill({
          fullName: "",
          dateOfBirth: "",
          nic: "",
          passport: "",
          advancedLevel: "",
          ordinaryLevel: "",
          higherStudies: "",
          gender: "",
          profession: "",
        }),
      ],
      true
    );
  };

  const { currentStepIndex, step, next, previous, isFirstStep, isLastStep } =
    useMultiStepForm([
      <FamilyDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <FamilyMembersDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
        handleAddFamilyMember={handleAddFamilyMember}
      />,
      <FamilyMemberDiseaseDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
        handleAddFamilyMember={handleAddFamilyMember}
      />,
      <HomeDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <VehicleDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <Summary values={{ ...formik.values }} />,
    ]);

  return (
    <NewRecordContainer>
      <div>
        <StepIndicator
          labels={["Family", "Members", "Deices", "Home", "Vehicle", "Summary"]}
          currentStepIndex={currentStepIndex}
        />
        <form action="#" onSubmit={formik.handleSubmit}>
          <div style={{ minHeight: 350 }}>{step}</div>
          <ControllerButtons>
            <button
              onClick={previous}
              type="button"
              className="btn btn-secondary"
              style={{ opacity: isFirstStep ? ".6" : "1" }}
            >
              Back
            </button>
            <button type="submit" className="btn btn-primary">
              {isLastStep ? "Submit" : "Next"}
            </button>
          </ControllerButtons>
        </form>
      </div>
    </NewRecordContainer>
  );
};

export default NewRecord;

this useMultiStepForm hook

import { useState } from "react";

export function useMultiStepForm(steps) {
  const [currentStepIndex, setCurrentStepIndex] = useState(0);

  function next() {
    setCurrentStepIndex((prev) => {
      if (prev >= steps.length - 1) return prev;
      return prev + 1;
    });
  }

  function previous() {
    setCurrentStepIndex((prev) => {
      if (prev <= 0) return prev;
      return prev - 1;
    });
  }

  function goTo(index) {
    setCurrentStepIndex(index);
  }

  return {
    currentStepIndex,
    step: steps[currentStepIndex],
    next,
    previous,
    goTo,
    steps,
    isFirstStep: currentStepIndex === 0,
    isLastStep: currentStepIndex === steps.length - 1,
  };
}

Edited here is the record schema

import * as Yup from "yup";

export const recordSchema = (step) => {
  return Yup.lazy((values) => {
    if (step === step) { // I want make familyMembersDetails array required if current step 2
      return Yup.object({
        ...values,
        familyMembersDetails: Yup.array()
          .of(
            Yup.object({
              fullName: Yup.string().required("Full Name is required"),
              dateOfBirth: Yup.string().required("Date of birth is required"),
              nic: Yup.string(),
              passport: Yup.string(),
              advancedLevel: Yup.string(),
              ordinaryLevel: Yup.string(),
              higherStudies: Yup.string(),
              gender: Yup.string().required("Select Gender"),
              profession: Yup.string(),
            })
          )
          .required("Family members details are required"),
      });
    } else {
      return Yup.object({
        houseHolderName: Yup.string()
          .max(25, "Must be 25 characters or less")
          .required("House Holder Name is required"),
        email: Yup.string()
          .email("Invalid email address")
          .required("Email is required"),
        address: Yup.string().required("Address is required"),
        phone: Yup.string().required("Phone number is required"),
        houseHolderNo: Yup.string().required(
          "House Holder Number is required."
        ),
        numberOfFamilyMembers: Yup.string().required(
          "Number of family member is required"
        ),
      });
    }
  });
};


  • What is `recordSchema`? Does `recordSchema` need `currentStepIndex` on the initial render cycle, or can it handle changing `currentStepIndex` values during the life of the component? – Drew Reese May 24 '23 at 06:25
  • I'm not that familiar with formik, if during the life of the component a new/different `step` argument value is passed to `recordSchema`, e.g. `validationSchema`, does `formik` pick up on that change and the validation responds accordingly? Or is it all set when the component mounts and `useFormik` is called on the initial render? – Drew Reese May 24 '23 at 07:38
  • Is there a correct way to pass the currentStepIndex dynamically to the validationSchema in useFormik? Am I approaching this problem correctly, or is there an alternate way to handle dynamic validation schema based on the current step? I have tried some approaches so far (including the suggestions received), but I haven't been able to resolve the issue. Any insights, alternative approaches, or potential mistakes in my code would be greatly appreciated – Mohamed Rifkan May 24 '23 at 07:46
  • You can't swap the order of the hooks because this will just cause the same issue for `useMultiStepForm` with the `formik` variable. I was thinking you could cache the `currentStepIndex` in a React ref and pass this to the `recordSchema` function. I can provide an answer with explanation and code example if you like. – Drew Reese May 24 '23 at 07:49
  • Dynamically Yup receives the changes and make some fields required, for example If user in second step **familyMemersDetails** array should be required otherwise dont – Mohamed Rifkan May 24 '23 at 07:50

1 Answers1

0

You can't just swap useMultiStepForm before useFormik because it relies on formik being declared and would create effectively the same issue.

I think a sensible solution would be to cache the currentStepIndex value in a React ref that is passed to the recordSchema component.

Example:

export const recordSchema = (stepRef) => {
  return Yup.lazy((values) => {
    const step = stepRef.current; // <-- unpack current step value

    if (step === 2) { // I want make familyMembersDetails array required if current step 2
      return Yup.object({
        ...
      });
    } else {
      return Yup.object({
        ...
      });
    }
  });
};
const NewRecord = () => {
  const stepRef = React.useRef(); // <-- ref to hold current step index value

  const formik = useFormik({
    initialValues: {
      ...
    },
    validationSchema: recordSchema(stepRef), // <-- pass ref
    onSubmit: (data) => {
      if (!isLastStep) return next();
    },
  });

  ...

  const { currentStepIndex, step, next, previous, isFirstStep, isLastStep } =
    useMultiStepForm([ ... ]);

  React.useEffect(() => {
    stepRef.current = currentStepIndex; // <-- cache current step index value
  }, [currentStepIndex]);

  return (
    ...
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181