4

I am working on a scenario where I have to do a multi-step form which I have already done, as well as the validation part. I am using react-hook-form for validation.

I have multi-step form:

  • in the first form I have several fields and one radio button
  • by default radio button is ticked on for auto generated pass so in this case I have nothing to do
  • the second one is let me create a password so in this case one input field will be show and the user will create the password

Issue

In my final form I am doing the validation like below:

{
  fields: ["uname", "email", "password"], //to support multiple fields form
  component: (register, errors, defaultValues) => (
    <Form1
      register={register}
      errors={errors}
      defaultValues={defaultValues}
    />
  )
},

So to validate uname, email and password I am passing the values like above.

But when the radio button is ticked for auto generated password it is still handling the validation, I click on next and it is not going to next for because of password field.

And if I check the radio button as let me create the password it goes to next form and when I came back by clicking back it is going to auto generated password again and it is not holding the previous state. For other input fields it is handling the previous values but not in case of radio button scenario.

My full working code sandbox

2
  • have you seen this video: youtube.com/watch?v=CeAkxVwsyMU Commented Jul 14, 2020 at 6:10
  • @Bill yes already, but there they have used routing, I am not using routing for this thing, I never should use that. Commented Jul 14, 2020 at 6:40

2 Answers 2

1
+100

Answer 1 The reason is you fields: ["uname", "email", "password"] is fixed, password is always to be taken validation. Solution Need to store state of Form1 in App so you can check if the state of auto generated password is on remove password from the list

App.js

... other code
// need to move state and function form Form to app
const [show_input, setshow_input] = useState(false);

  const createInput = () => {
    setshow_input(true);
  };
  const auto_text = () => {
    setshow_input(false);
  };
  const forms = [
    {
      // validate based on show_input state
      fields: show_input ? ["uname", "email", "password"] : ["uname", "email"], //to support multiple fields form
      component: (register, errors, defaultValues) => (
        <Form1
          register={register}
          errors={errors}
          defaultValues={defaultValues}
          auto_text={auto_text}
          createInput={createInput}
          show_input={show_input}
        />
      )
    },
    {
      fields: ["lname"],
      component: (register, errors, defaultValues) => (
        <Form2
          register={register}
          errors={errors}
          defaultValues={defaultValues}
        />
      )
    },
    {
      component: (register, errors, defaultValues) => (
        <Form3
          register={register}
          errors={errors}
          defaultValues={defaultValues}
        />
      )
    }
  ];
... other code

Answer 2 When you go next the Form1 is unmounted so its state is destroyed. When you store Form1's state in App.js you will fix this issue too

Bonus: It's prefered to use camalCase (E.g: showInput) rather than underscore (show_input)

Edit serene-fast-jj8br

Sign up to request clarification or add additional context in comments.

1 Comment

Hey was using your code, and got stuck at one place I have put up a new question could you please check that stackoverflow.com/questions/64241134/hot-validate-react-form
0

The main problem is that you render the forms conditionally so all the previous form values are removed. The solution for this is to keep all forms mounted and just use display: none or display: block depending on which form is selected. This way all values will be persisted whenever you go to next or prev form or submit the form.

The second problem that you didn't remove the password field when it's unmounted so when moveToNext is called the valid argument in triggerValidation callback is always false. I fixed that by setting the fields for Form1 conditionally depending on if the password input is visible or not.

The third problem you are using defaultValues for the wrong purpose. You can get the current form values using getValues() which will return all the current values of the form.

I set the default value for uname field just as an example to show you how defaultValues should be used.

you can check the full solution here: https://codesandbox.io/s/fragrant-forest-75pzs?file=/src/App.js

here are all the changed files:

App.js

import React, { useState } from "react";
import Form1 from "./components/Form1";
import Form2 from "./components/Form2";
import Form3 from "./components/Form3";
import { useForm } from "react-hook-form";

function MainComponent() {
  const {
    register,
    triggerValidation,
    defaultValues,
    errors,
    getValues
  } = useForm({
    // You can set default values here
    defaultValues: {
      uname: "Lol"
    }
  });
  console.log("Errors: ", errors);
  const [currentForm, setCurrentForm] = useState(0);

  // control password input visibility and Form1 fields
  const [passwordVisible, setPasswordVisible] = useState(false);

  const showPassword = () => {
    setPasswordVisible(true);
  };
  const hidePassword = () => {
    setPasswordVisible(false);
  };

  const forms = [
    {
      fields: passwordVisible
        ? ["uname", "email", "password"]
        : ["uname", "email"],
      component: (register, errors) => (
        <Form1
          // a key is needed to render a list
          key={0}
          // this will be used to set the css display property to block or none on each form
          shouldDisplay={currentForm === 0}
          register={register}
          errors={errors}
          showPassword={showPassword}
          hidePassword={hidePassword}
          passwordVisible={passwordVisible}
        />
      )
    },
    {
      fields: ["lname"],
      component: (register, errors) => (
        <Form2
          key={1}
          shouldDisplay={currentForm === 1}
          register={register}
          errors={errors}
        />
      )
    },
    {
      component: (register, errors) => (
        <Form3
          key={2}
          shouldDisplay={currentForm === 2}
          register={register}
          errors={errors}
          values={getValues()}
        />
      )
    }
  ];

  const moveToPrevious = () => {
    triggerValidation(forms[currentForm].fields).then(valid => {
      if (valid) setCurrentForm(currentForm - 1);
    });
  };

  const moveToNext = () => {
    triggerValidation(forms[currentForm].fields).then(valid => {
      if (valid) setCurrentForm(currentForm + 1);
    });
  };

  const prevButton = currentForm !== 0;
  const nextButton = currentForm !== forms.length - 1;
  const handleSubmit = e => {
    console.log("whole form data - ", getValues());
  };
  return (
    <div>
      <div className="progress">
        <div>{currentForm}</div>
      </div>

      {forms.map(form => form.component(register, errors))}

      {prevButton && (
        <button
          className="btn btn-primary"
          type="button"
          onClick={moveToPrevious}
        >
          back
        </button>
      )}
      {nextButton && (
        <button className="btn btn-primary" type="button" onClick={moveToNext}>
          next
        </button>
      )}

      {currentForm === 2 && (
        <button
          onClick={handleSubmit}
          className="btn btn-primary"
          type="submit"
        >
          Submit
        </button>
      )}
    </div>
  );
}

export default MainComponent;

Form1

import React from "react";

function Form1({
  register,
  errors,
  shouldDisplay,
  passwordVisible,
  showPassword,
  hidePassword
}) {
  return (
    <div style={{ display: shouldDisplay ? "block" : "none" }}>
      <form autoComplete="on">
        <br />
        <div className="form-group">
          <label>User name</label>
          <input type="text" name="uname" ref={register({ required: true })} />
          {errors.uname && <span>required</span>}
          <label>Email</label>
          <input type="email" name="email" ref={register({ required: true })} />
          {errors.email && <span>required</span>}
        </div>
        <div>
          <div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
            <label className="form_label">Password</label>
            <div className="form-check">
              <label>
                <input
                  type="radio"
                  name="auto_pass"
                  id="Radios1"
                  value="auto_pass"
                  className="form-check-input"
                  defaultChecked={true}
                  onChange={hidePassword}
                />
                Auto generated password
              </label>
            </div>
            <div className="form-check">
              <label>
                <input
                  type="radio"
                  name="auto_pass"
                  id="Radios2"
                  value="let_me"
                  className="form-check-input"
                  onChange={showPassword}
                />
                Let me create the password
              </label>
            </div>
          </div>
          {passwordVisible && (
            <div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mb-3">
              <label className="form_label">Password</label>
              <input
                type="password"
                name="password"
                className="form-control"
                ref={register({ required: true })}
              />
              {errors.password && (
                <span className="text-danger">Password is reguired</span>
              )}
            </div>
          )}
        </div>
      </form>
    </div>
  );
}

export default Form1;

Form2

import React from "react";

function Form2({ register, errors, shouldDisplay }) {
  return (
    <div style={{ display: shouldDisplay ? "block" : "none" }}>
      <form autoComplete="on">
        <br />
        <div className="form-group">
          <label>User last name</label>
          <input type="text" name="lname" ref={register({ required: true })} />
          {errors.lname && <span>required</span>}
        </div>
      </form>
    </div>
  );
}

export default Form2;

Form3

import React from "react";

function Form3({ values, shouldDisplay }) {
  return (
    <div style={{ display: shouldDisplay ? "block" : "none" }}>
      <h3>Want to display all values here like below</h3>
      {Object.entries(values).map(([key, value]) => (
        <p key={key}>
          {key}: {value}
        </p>
      ))}

      <br />
      <p>So that use can check for any Wrong info</p>
    </div>
  );
}

export default Form3;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.