skip to Main Content

I have a JS objects like below (this is just one of many)

const formInput= {
  Service_Name: {
    required: true,
    type: "text",
    constraint: {
      min: 3,
      max: 15,
      format: "alpha-hyphen-numeric"
    }
  },
  Content_Type: {
    required: true,
    type: "select",
    options: ["Markdown", "HTML"]
  },
  Remarks: {
    required: false,
    type: "text",
    constraint: {
      max: 30
    }
  }
};

I have to create form to capture the inputs of these fields. Since there are multiple JS objects (in reality they are json‘s) like this the form creation has to be dynamic. I have accomplished this like so

import React from "react";
import { useForm } from "react-hook-form";
import { TextInput } from "./textInput";
import { SelectInput } from "./selectInput";
import { FiledType } from "./formTypes";

const formInput: FiledType = {
  Service_Name: {
    required: true,
    type: "text",
    constraint: {
      min: 3,
      max: 15,
      format: "alpha-hyphen-numeric"
    }
  },
  Content_Type: {
    required: true,
    type: "select",
    options: ["Markdown", "HTML"]
  },
  Remarks: {
    required: false,
    type: "text",
    constraint: {
      max: 30
    }
  }
};

export default App = (): JSX.Element => {
  const form = useForm({ mode: "all" });

  const { register, handleSubmit, formState } = form;
  const { errors } = formState;

  const onSubmit = (data: any) => {
    console.log(data);
  };

  return (
    <React.Fragment>
      <div>
        <form onSubmit={handleSubmit(onSubmit)} noValidate>
          {Object.keys(formInput).map((item) =>
            formInput[item].type === "text" ? (
              <TextInput
                key={item}
                name={item}
                formItemData={formInput[item]}
                register={register}
                errors={errors}
              />
            ) : (
              <SelectInput
                key={item}
                name={item}
                formItemData={formInput[item]}
                register={register}
                errors={errors}
              />
            )
          )}
          <button>Submit</button>
        </form>
      </div>
    </React.Fragment>
  );
};

Based on "type" in the json, one of the TextInput, DateInput or SelectInput is rendered. The TextInput component is like below.

import React from "react";
import { FormFieldPropType } from "./formTypes";

export const TextInput = ({
  register,
  name,
  formItemData,
  errors
}: FormFieldPropType) => {
  const formName = name.replace(/s+/g, "-").toLowerCase();

  return (
    <div>
      <label htmlFor={formName}>{name}</label>
      <div>
        <input
          {...register(formName, {
            required: formItemData["required"],
            maxLength: formItemData["constraint"]?.max,
            minLength: formItemData["constraint"]?.min
          })}
          type="text"
          id={formName}
          name={formName}
          placeholder={name}
        />
      </div>
    </div>
  );
};

All of this is working fine. However, now there is a requirement to render additional field(s) based on the content of another filed. For example, if the user selects Markdown for the field "Content Type", I should add a "text" filed right under it named "Folder Location". If the user selects HTML, nothing needs to be done.

I am not able to figure out how to add this additional filed conditionally. Can any one help? Thanks.

The working code (without syyling) can be found here -> https://codesandbox.io/s/reac-hook-form-r66n5w?file=/src/textInput.tsx

2

Answers


  1. To achieve this, you will need to keep track of the values of your form fields. Fortunately, react-hook-form provides the watch function, which allows you to monitor the value of a specified input. Using watch, you can conditionally render the additional "Folder Location" field when the "Content Type" is "Markdown".

    Here’s how you can implement this:

    Use watch to monitor the Content_Type field
    First, import the watch function from your form object, and use it to watch the Content_Type field:

    const { register, handleSubmit, formState, watch } = form;
    const contentTypeValue = watch("content-type");
    

    Conditionally render the "Folder Location" input
    You can now check the contentTypeValue to decide whether to render the "Folder Location" input:

    {contentTypeValue === "Markdown" && (
    <TextInput
    name="Folder Location"
    formItemData={{
      required: true,
      type: "text",
      constraint: {
        max: 30,
      },
      }}
     register={register}
     errors={errors}
     />
    )}
    

    This code will render the "Folder Location" input right below the "Content Type" input if the "Markdown" option is selected.

    Update the App component
    Incorporate the above changes into your App component:

    export default App = (): JSX.Element => {
    const form = useForm({ mode: "all" });
    
    const { register, handleSubmit, formState, watch } = form;
    const { errors } = formState;
    
    const contentTypeValue = watch("content-type");
    
    const onSubmit = (data: any) => {
    console.log(data);
    };
    
    return (
    <React.Fragment>
      <div>
        <form onSubmit={handleSubmit(onSubmit)} noValidate>
          {Object.keys(formInput).map((item) => (
            <React.Fragment key={item}>
              {formInput[item].type === "text" ? (
                <TextInput
                  name={item}
                  formItemData={formInput[item]}
                  register={register}
                  errors={errors}
                />
              ) : (
                <SelectInput
                  name={item}
                  formItemData={formInput[item]}
                  register={register}
                  errors={errors}
                />
              )}
              {item === "Content_Type" && contentTypeValue === "Markdown" && 
    (
                <TextInput
                  name="Folder Location"
                  formItemData={{
                    required: true,
                    type: "text",
                    constraint: {
                      max: 30,
                    },
                  }}
                  register={register}
                  errors={errors}
                />
              )}
            </React.Fragment>
          ))}
          <button>Submit</button>
        </form>
       </div>
     </React.Fragment>
     );
    };
    

    By implementing the above changes, you’ll be able to render the "Folder Location" field conditionally based on the value of the "Content Type" field. Make sure to adjust the rest of your components accordingly to handle the newly added "Folder Location" field (e.g., validating its input if necessary). Let me know if this helps you mate, if you have anymore questions feel free to ask

    Login or Signup to reply.
  2. If Derrick’s proposed soultion didn’t work for you for some reason, Try this alternative.(tho Derrick’s solution seems more elegant)
    In the App component include a state for the selected content type and an eventHandler to update the state. Then passdown the handler to Select component

    const [contentType, setContentType] = useState(""); // State for selected content type
    const handleContentTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
        setContentType(event.target.value); // Update the selected content type in state
    }
      
    <SelectInput
      ... other props
      onChange={handleContentTypeChange} // Pass the onChange handler to the SelectInput component
    />  
    

    In the SelectInput component, pass the onChange prop to the select element to handle the selection change

    <select
      ... other props
      onChange={onChange} // Pass the onChange handler
    >
    

    Add the to be conditionally rendered component to your form.

    <form onSubmit={handleSubmit(onSubmit)}>
        {Object.keys(formInput).map((item) 
        ...
        )}
        {/* Conditional rendering of the "Folder Location" field */}
        {contentType === "Markdown" && (
        <TextInput
            name="Folder_Location"
            formItemData={{
            required: true,
            type: "text",
            constraint: {
                max: 50,
            },
            }}
            register={register}
            errors={errors}
        />
        )}          
        <button>Submit</button>
    </form>
    

    Also you will need to update FormFieldPropType

    export type FormFieldPropType = {
      register: UseFormRegister<FieldValues>;
      name: string;
      formItemData: FormItemData;
      errors: FieldErrors<FieldValues>;
      onChange : (event: React.ChangeEvent<HTMLSelectElement>) => void, // Add the type
    }
    

    edit: here’s a working sandbox link

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search