0

I have components where In each I have one form on click of next I am opening a new form( calling new component) and when it is last I am changing it to submit.

What I want to achieve

1: on each next and back click I want to validate my form using react-form-hook

2: on click of submit I should able to see all the data

3: while clicking on next to back data should not loss from inputs

Issue

So for validation I am using react-hook-form as it is very simple to use, but here I am not able to find out as my buttons are not inside the forms they are in main component so How will I validate and on submit the form on submit click wit all data.

My code

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

function AddCompanyMain() {
  const [currState, setCurrState] = useState(1);
  const [allState, setAllstate] = useState(3);

  const moveToPrevious = () => {
    setCurrState(currState - 1);
  };
  const moveToNext = () => {
    setCurrState(currState + 1);
  };

  return (
    <div>
      <div class="progress">
        <div>{currState}</div>
      </div>

      {currState === 1 && <Form1 />}
      {currState === 2 && <Form2 />}
      {currState === 3 && <Form3 />}

      {currState !== 1 && (
        <button
          className="btn btn-primary"
          type="button"
          onClick={moveToPrevious}
        >
          back
        </button>
      )}
      {currState !== allState && (
        <button className="btn btn-primary" type="button" onClick={moveToNext}>
          next
        </button>
      )}

      {currState === 3 && (
        <button className="btn btn-primary" type="submit">
          Submit
        </button>
      )}
    </div>
  );
}

export default AddCompanyMain;

My Full code Code sandbox

I am just looking for a good approach because here I can't put submit in each component, because I have to show steps at the top.

2 Answers 2

1
+50

You can solve your problem by doing the following:

  • In app.js, store the forms in an array of objects and render them based on the step(currentFrom state).
  • Call useForm in parent component (not in child Form component) and pass register, errors, defaultValues values as props to your Form components.
  • In your Form components, you need to grab register, etc as props
  • Maintain defaultValues in a state and update them in the onClick of next/previous. You need to getValues and then use setValues to set the state(defaultValues)

Also:

  • In order to trigger validation on next/previous button click, you need to use triggerValidation

  • In order to retain values when you click next/previous, you need to use defaultValues prop

See the working demo is here

Code Snippet

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 AddCompanyMain() {
  const {
    register,
    triggerValidation,
    errors,
    setValue,
    getValues
  } = useForm();
  const [defaultValues, setDefaultValues] = useState({});

  const forms = [
    {
      fields: ["uname"],
      component: (register, errors, defaultValues) => (
        <Form1
          register={register}
          errors={errors}
          defaultValues={defaultValues}
        />
      )
    },
    {
      fields: ["lname"],
      component: (register, errors, defaultValues) => (
        <Form2
          register={register}
          errors={errors}
          defaultValues={defaultValues}
        />
      )
    },
    {
      fields: ["company"],
      component: (register, errors, defaultValues) => (
        <Form3
          register={register}
          errors={errors}
          defaultValues={defaultValues}
        />
      )
    }
  ];

  const [currentForm, setCurrentForm] = useState(0);

  const moveToPrevious = () => {
    setDefaultValues(prev => ({ ...prev, [currentForm]: getValues() }));

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

  const moveToNext = () => {
    console.log(getValues());
    setDefaultValues(prev => ({ ...prev, [currentForm]: getValues() }));
    triggerValidation(forms[currentForm].fields).then(valid => {
      if (valid) setCurrentForm(currentForm + 1);
    });
  };

  const prevButton = currentForm !== 0;
  const nextButton = currentForm !== forms.length - 1;

  return (
    <div>
      <div class="progress">
        <div>{currentForm}</div>
      </div>

      {forms[currentForm].component(
        register,
        errors,
        defaultValues[currentForm]
      )}

      {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 === 3 && (
        <button className="btn btn-primary" type="submit">
          Submit
        </button>
      )}
    </div>
  );
}

export default AddCompanyMain;

Note - In general, when working with multi step forms, it is better to use controlled components and maintain the states in the parent component

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

10 Comments

Hey your solution is fine if each form has one one input field here in my case I have multiple input fields in each form, suppose for example I have 2 input in my form one and i.e is age so in main components in field array I am passing the second value i.e age so it is working fine but when I come back i.e from from 2 to 1 the values both input fields are taking is same i.e what ever i am passing in u name the age is taking the same.
umm... give me the codesandbox where u've put the age, i'll fix it
codesandbox.io/s/determined-glade-ei1in?file=/src/App.js Hey here is my code sandbox, age I have just taken for example the main point is forms can have multiple fields none of the form is going to have one field.
just checked - in your example, uname & age are taking same values because you have set the age input defaultValue incorrectly. i.e. you have used defaultValues.uname instead of .age. So for age input do defaultValue={defaultValues && defaultValues.age} ... hope its all good now...
Hey I didn't get your solution could you please share the codesandbox link.
|
1

You can lift the value state of each component up to the parent, and then pass an onChange prop to each to update the state from each child. I'm not sure how this will play with react-hooks-form, but this is generally how you want to synchronize states of child components.

import React from "react";
import "./styles.css";

const FormOne = ({ value, onChange }) => (
  <div>
    Form 1
    <input type='text' value={value} onChange={e => onChange(e.target.value)} />
  </div>

)

const FormTwo = ({ value, onChange }) => (
  <div>
    Form 2
    <input type='text' value={value} onChange={e => onChange(e.target.value)} />
  </div>
)

const FormThree = ({ value, onChange }) => (
  <div>
    Form 3
    <input type='text' value={value} onChange={e => onChange(e.target.value)} />
  </div>
)

export default function App() {
  const [formOne, setFormOne] = React.useState('')
  const [formTwo, setFormTwo] = React.useState('')
  const [formThree, setFormThree] = React.useState('')
  const [index, setIndex] = React.useState(0)

  const moveUp = (e) => {
    e.preventDefault()

    if (index !== 2) {
      setIndex(index => index += 1)
    }
  }

  const moveDown = (e) => {
    e.preventDefault()

    if (index !== 0) {
      setIndex(index => index -= 1)
    }
  }

  const handleSubmit = (e) => {
    e.preventDefault()

    console.log(formOne, formTwo, formThree)
  }

  const form = index === 0
    ? <FormOne value={formOne} onChange={setFormOne} />
    : index === 1
    ? <FormTwo value={formTwo} onChange={setFormTwo} />
    : <FormThree value={formThree} onChange={setFormThree} />

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        {form}
        {index !== 0 && <button onClick={moveDown}>Back</button>}
        {index !== 2
          ? <button onClick={moveUp}>Next</button>
          : <button type='submit'>Submit</button>
        }
      </form>
    </div>
  );
}

1 Comment

Check the edit. Lots of optimizations to be made here, but this should get you started.

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.