22

Here is my form looks like and also CodeSanbox. currently I'm using react-hook-form
as you can see form has 3 inputs. Submit button should be disabled until all the required fields are entered. Two use case:

  1. If "Check" is unchecked:
    • only "id" should be validated and submit button should get enabled. "firt" and "last" names should not be part of form data
  2. If "Check" is checked
    • all the fields should be validated
      first and last names are only required if "Check" is checked. so its not checked then form should only validate "ID" field. if "Check" is checked then all fields should get validated.

problem I'm having is if I enter id, form state is still "invalid". Form is expecting to enter values for first and last name.
I would appreciate any help.

Form

5 Answers 5

22
+300

I have updated your CodeSanBox code and also adding the full code here:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";

import "./index.css";

function App() {
  const {
    register,
    handleSubmit,
    errors,
    formState,
    unregister,
    setValue,
    getValues,
    reset
  } = useForm({
    mode: "onBlur",
    reValidateMode: "onBlur",
    shouldUnregister: true
  });
  //console.log(formState.isValid);
  console.log(errors);
  const [disabled, setDisabled] = useState(true);
  const onSubmit = (data) => {
    alert(JSON.stringify(data));
  };
  useEffect(() => {
    // @ts-ignore

    if (disabled) {
      console.log("unregister");
      reset({ ...getValues(), firstName: undefined, lastName: undefined });
      unregister(["firstName", "lastName"]);
    } else {
      console.log("register");
      register("firstName", { required: true });
      register("lastName", { required: true });
    }
  }, [disabled]);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="id">ID</label>
      <input
        name="id"
        placeholder="id"
        ref={register({ required: true, maxLength: 50 })}
      />
      {errors.id && <p>"ID is required"</p>}
      <fieldset disabled={disabled}>
        <legend>
          <input
            type="checkbox"
            name={"name"}
            ref={register}
            onClick={() => setDisabled(!disabled)}
          />
          <span>Check</span>
        </legend>
        <label htmlFor="firstName">First Name</label>
        <input
          name="firstName"
          placeholder="Bill"
          onChange={(e) => {
            console.log(e.target.value);
            setValue("firstName", e.target.value);
          }}
          ref={register({ required: !disabled })}
        />
        {errors.firstName && <p>"First name is required"</p>}
        <label htmlFor="lastName">Last Name</label>
        <input
          name="lastName"
          placeholder="Luo"
          onChange={(e) => setValue("lastName", e.target.value)}
          ref={register({ required: !disabled })}
        />
        {errors.lastName && <p>"Last name is required"</p>}
      </fieldset>

      <input type="submit" disabled={!formState.isValid} />
    </form>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

First I found that you set disabled state as false which should be true as an initial value, and regarding the issue, I have used reset and getValues functions when the disabled state changes.

EDIT for you to recognize code changes easy, I have restored all the code at CodeSanBox.

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

3 Comments

How to do the same when using resolver like Joi or Yup for validation, Is there any way to dynamically change resolver validation depending upon conditions ?
@NabeelHussainShah Check out the when clause if you are using Yup
@callmekatootie need to give u a medal, when is what I need man, thank youuuuu
7

This whole validation behavior (UX) is definitely making things a bit harder, however, there are a couple of things that you should leverage from the library such as:

  • watch
  • validate
  • getValues
import React from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";

import "./index.css";

function App() {
  const {
    register,
    handleSubmit,
    errors,
    formState: { isValid, touched },
    getValues,
    trigger,
    watch
  } = useForm({
    mode: "onBlur"
  });
  const onSubmit = (data) => {
    alert(JSON.stringify(data));
  };
  const validate = (value) => {
    if (getValues("name")) { // read the checkbox value
      return !!value;
    }

    return true;
  };
  const isChecked = watch("name"); // watch if the name is checked

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="id">ID</label>
      <input
        name="id"
        placeholder="id"
        ref={register({ required: true, maxLength: 50 })}
      />
      {errors.id && <p>"ID is required"</p>}

      <fieldset disabled={!isChecked}>
        <legend>
          <input
            type="checkbox"
            name={"name"}
            ref={register}
            onChange={() => trigger()} // you want update isValid due to state change, and also those extra two inputs become required
          />
          <span>Check</span>
        </legend>
        <label htmlFor="firstName">First Name</label>
        <input
          name="firstName"
          placeholder="Bill"
          ref={register({
            validate
          })}
        />
        // make sure input is touched before fire an error message to the user
        {errors.firstName && touched["firstName"] && (
          <p>"First name is required"</p>
        )}
        <label htmlFor="lastName">Last Name</label>
        <input
          name="lastName"
          placeholder="Luo"
          ref={register({
            validate
          })}
        />
        {errors.lastName && touched["lastName"] && (
          <p>"Last name is required"</p>
        )}
      </fieldset>

      <input type="submit" disabled={!isValid} />
    </form>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

CSB: https://codesandbox.io/s/react-hook-form-conditional-fields-forked-n0jig?file=/src/index.js:0-1831

Comments

1

on your ref, dont use hard coded bool true, ref={register({ required: true})}, but your dynamic ref={register({ required: disabled })}

do notice that because your mode: "onBlur" config, the button won't be abled until id field blurred

1 Comment

thx @hagai , i modified my code as per your suggestion, it is still not behaving as desire. also, mode:'onblur" is intentional to have formState.isValid , "formState.isValid" is not applicable in case of mode:"onSubmit"
1

You just need to replace true .from ref: required:true..... Instead use const 'disabled' ....in input of first and last name .

So as to achieve dynamic change

1 Comment

thx @nihal , i modified my code as per your suggestion, it is still not behaving as desire. also, mode:'onblur" is intentional to have formState.isValid , "formState.isValid" is not applicable in case of mode:"onSubmit"
1

Same problem. That's what I came up with. https://stackblitz.com/edit/react-ts-b557bc?file=App.tsx

import * as React from 'react';
import './style.css';
import { useForm, useWatch, Controller } from 'react-hook-form';

export default function App() {
  const {
    register,
    handleSubmit,
    control,
    unregister,
    formState: { errors },
  } = useForm({
    defaultValues: {
      isOnline: true,
      isRegular: false,
      urlAddress: '',
      cityAddress: '',
      startDate: '',
    },
  });

  const [isOnline, isRegular] = useWatch({
    control,
    name: ['isOnline', 'isRegular'],
  });

  const onSubmit = (data) => console.log('data: ', data);
  console.log('errors: ', errors);

  React.useEffect(() => {
    if (isOnline) {
      register('urlAddress');
      unregister('cityAddress');
    } else {
      register('cityAddress');
      unregister('urlAddress');
    }
  }, [register, unregister, isOnline]);

  React.useEffect(() => {
    if (isRegular) {
      register('startDate');
    } else {
      unregister('startDate');
    }
  }, [register, unregister, isRegular]);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        control={control}
        name="isOnline"
        render={({ field: { onChange, value } }) => (
          <input
            type="checkbox"
            checked={value}
            value={value}
            onChange={onChange}
          />
        )}
      />

      {isOnline ? (
        <Controller
          control={control}
          name="urlAddress"
          rules={{ required: { value: true, message: 'urlAddress required' } }}
          render={({ field: { onChange, value }, fieldState: { error } }) => (
            <div>
              <div>urlAddress</div>
              <input type="text" value={value} onChange={onChange} />
              error: {error?.message}
            </div>
          )}
        />
      ) : (
        <Controller
          control={control}
          name="cityAddress"
          rules={{ required: { value: true, message: 'cityAddress required' } }}
          render={({ field: { onChange, value }, fieldState: { error } }) => (
            <div>
              <div>cityAddress</div>
              <input type="text" value={value} onChange={onChange} />
              error: {error?.message}
            </div>
          )}
        />
      )}

      <Controller
        control={control}
        name="isRegular"
        render={({ field: { onChange, value } }) => (
          <input type="checkbox" value={value} onChange={onChange} />
        )}
      />

      {isRegular && (
        <Controller
          control={control}
          name="startDate"
          rules={{ required: { value: true, message: 'startDate required' } }}
          render={({ field: { onChange, value }, fieldState: { error } }) => (
            <div>
              <div>startDate</div>
              <input type="text" value={value} onChange={onChange} />
              error: {error?.message}
            </div>
          )}
        />
      )}

      <input type="submit" />
    </form>
  );
}

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.