0
votes

I want to set up a custom Form component using react-hook-form that can handle fields that are potentially nested or wrapped in other elements. My approach is to go through the component tree and recursively pass register and errors (returned by useForm()) to all leaf nodes that are input fields.

For example, a simple sign-up form. The first and last name fields are wrapped in a div that styles them to be on the same line. The form looks like this:

      <Form
        onSubmit={onSubmit}
        styles={["login_form"]}
        showButton
        buttonText='SIGN UP'>

        // These are the wrapped fields
        <div className='signup_name_field'>
          <TextInput name={"fname"} label={"first"} />
          <TextInput name={"lname"} label={"last"} />
        </div>

        <TextInput name={"email"} label={"email"} />
        <TextInput password={true} name={"password"} label={"password"} />
        <TextInput
          password={true}
          name={"passwordConfirm"}
          label={"confirm password"}
        />
      </Form>

I created a custom Form component following react-hook-form's example but added a recursive function to handle nested or wrapped fields:

  const recursiveInjectProps: any = (children: any) => {
    return React.Children.map(children, (child) => {
      if (child.props.children) {
        recursiveInjectProps(child.props.children);
        return child;
      } else {
        if (child.props.name) {
          return React.createElement(child.type, {
            ...{
              ...child.props,
              register: register,
              key: child.props.name,
            },
          });
        } else {
          return child;
        }
      }
    });
  };

  return (
    <form className={styles.join(" ")} onSubmit={handleSubmit(onSubmit)}>
      {recursiveInjectProps(children)}
      {renderButton()}
    </form>
  );

And TextInput looks like this:


  const checkRegister = () => {
    if (register) {
      return (
        <div className='login_field'>
          <input
            className='login_input'
            name={name}
            placeholder={label}
            ref={register(validation)}
            type={password ? "password" : "text"}
            {...rest}
          />
          <label htmlFor={name} className='login_label' />
          {errors && errors[name] && errors[name].message}
        </div>
      );
    } else {
      return <div>no dice</div>;
    }
  };
  return checkRegister();

The issue is that recursiveInjectProps() is failing to inject register and errors for children that are more than one layer deep (so, the name fields that are wrapped in the div). I know this because when it renders I see "no dice" where the name fields should be.

Would really appreciate any help with this.

1

1 Answers

0
votes

Perhaps you should use context API: https://react-hook-form.com/api#useFormContext

import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";

export default function App() {
  const methods = useForm();
  const onSubmit = data => console.log(data);

  return (
    <FormProvider {...methods} > // pass all methods into the context
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <NestedInput />
        <input type="submit" />
      </form>
    </FormProvider>
  );
}

function NestedInput() {
  const { register } = useFormContext(); // retrieve all hook methods
  return <input name="test" ref={register} />;
}