Search code examples
javascriptreactjsreact-hook-formreact-forms

react-hook-form use in component to pass props for dynamic text fields


Original question

I have the following component

import React from 'react';
import PropTypes from 'prop-types';
import { useForm } from 'react-hook-form';

export default function FormField({ type, name, formRegister, errorMsg, state }) {
  const methods = useForm();
  const { register } = methods;

  return (
    <>
      <div>
        <label htmlFor={name} className="block text-sm font-medium text-gray-900">
          Password
        </label>

        <div className="mt-1">
          <input
            type={type}
            name={name}
            {...register({ formRegister }, { required: { errorMsg } })}
            className="py-3 px-4 block w-full shadow-sm text-gray-900 focus:ring-blue-700 focus:border-blue-900 border-gray-300 rounded-md"
            onChange={(event) => {state}(event.target.value)}
          />
        </div>
      </div>
    </>
  );
}

FormField.PropTypes = {
  type: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  formRegister: PropTypes.string.isRequired,
  errorMsg: PropTypes.string.isRequired,
  state: PropTypes.string.isRequired
};

FormField.defaultProps = {
  type: 'text',
  name: 'text',
  formRegister: 'text',
  errorMsg: 'text is required',
  state: 'setName'
};

My goal for this component is to create a dynamic field component so I can do the following

<FormField
  type="password"
  name="password"
  formRegister="password"
  errorMsg="Password is required"
  state="setPassword"
/>

However, I'm having issues in passing in the react-form-hooks ...register

I get the following

TypeError: path.split is not a function

Any help would be great, cheers.

original edit

So the forms are now working thanks too knoefel, however, here is the issue, the error messages are now not showing.

So in my component I have everything needed for errors to be passed, however they are not showing, debugging this I've found that when I do this

<FormField
  {...register(email, { required: 'email is required' })}
  type="email"
  label="Email"
  name="email"
  errorMsg="Email is required"
  placeholder="[email protected]"
/>

The error now shows, so what gives? I already have this in the component?

Updated question #2

I now have the following component

import React from 'react';
import PropTypes from 'prop-types';

const FormField = React.forwardRef(
  ({ type, name, label, required, placeholder, ...props }, ref) => {
    return (
      <div>
        <label htmlFor={name} className="block text-sm font-medium text-gray-900">
          {label}
          <span className="text-red-500 font-bold text-lg">{required && '*'}</span>
        </label>

        <div className="mt-1">
          <input
            {...props}
            name={name}
            ref={ref}
            type={type}
            id={name}
            className={['field', `field--${type}`].join(' ')}
            placeholder={placeholder}
          />
        </div>
      </div>
    );
  }
);

export default FormField;

FormField.propTypes = {
  type: PropTypes.oneOf(['text', 'email', 'password', 'file', 'checkbox']),
  register: PropTypes.func,
  name: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  placeholder: PropTypes.string
};

FormField.defaultProps = {
  type: 'text',
  name: 'text',
  label: 'Label',
  placeholder: ''
};

and now the following page


import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import Background from '../../../public/images/option1.png';
import Router from 'next/router';
import { signIn } from 'next-auth/client';
import { useForm, FormProvider } from 'react-hook-form';

// components
import ErrorsPopup from '../../components/ErrorsPopup';
import FormField from '../../components/Forms/FormField';
import Button from '../../components/Button';

export default function Login() {
  const methods = useForm();
  const { handleSubmit, register } = methods;

  const onSubmit = async (data) => {
    await signIn('credentials', {
      redirect: false,
      data
    });

    Router.push('/dashboard');
  };

  return (
    <>
      <Head>
        <title>Ellis Development - Login</title>
      </Head>

      <div className="relative">
        <div className="md:flex">
          {/* Image */}
          <div className="flex items-center justify-center bg-blue-700 h-screen lg:w-96">
            <Image src={Background} width={350} height={350} layout="fixed" />
          </div>

          {/* Contact form */}
          <div className="flex flex-col justify-center px-6 sm:px-10 w-full">
            <h1 className="text-4xl font-extrabold text-grey-800">Login</h1>

            {/* errors */}
            <FormProvider {...methods}>
              <ErrorsPopup />
            </FormProvider>

            <form
              onSubmit={handleSubmit(onSubmit)}
              className="mt-6 flex flex-col gap-y-6 sm:gap-x-8">
              {/* email field */}
              <FormField
                {...register('email', {
                  required: 'Email is required',
                  pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
                })}
                type="email"
                label="Email"
                placeholder="[email protected]"
                required
              />

              {/* password field */}
              <FormField
                {...register('password', {
                  required: 'Password is required'
                })}
                type="password"
                label="Password"
                placeholder="*******"
                required
              />

              <div className="flex items-center justify-between sm:col-span-2">
                <div>
                  <Button type="submit" label="Login" icon="SaveIcon" />
                </div>

                <div>
                  <Link href="/dashboard/auth/register">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2 mr-4">
                      Register
                    </a>
                  </Link>

                  <Link href="/dashboard/auth/forgot">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2">
                      Forgot your password?
                    </a>
                  </Link>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </>
  );
}

The issue here now is that the form is still not submitting.

Any help would be great, cheers.

edit #2

This is what I get when submitting the form and also added console.log(formState.errors) enter image description here

Also when I fill the form out I get no errors, so could the issue be around on the onSubmit?.

Also if I console log the data I get this enter image description here


Solution

  • I made a working CodeSandbox after your last update of your question. You forgot to set the name prop of your <input />, so RHF had no chance to access the form fields. You also don't need useState here, as you can just access the form data in the callback of handleSubmit.

    You could also simplify your interface by using your name prop as the first argument for your register call. The spread of register will also return a name property set to the name of the first argument you pass to register.

    export default function Login() {
      const methods = useForm();
      const { handleSubmit, register } = methods;
    
      const onSubmit = async (data) => {
        console.log(data);
      };
    
      return (
        <>
          <Head>
            <title>Ellis Development - Login</title>
          </Head>
    
          <div className="relative">
            <div className="md:flex">
              {/* Image */}
              <div className="flex items-center justify-center bg-blue-700 h-screen lg:w-96"></div>
    
              {/* Contact form */}
              <div className="flex flex-col justify-center px-6 sm:px-10 w-full">
                <h1 className="text-4xl font-extrabold text-grey-800">Login</h1>
                <FormProvider {...methods}>{/* errors */}</FormProvider>
    
                <form
                  onSubmit={handleSubmit(onSubmit)}
                  className="mt-6 flex flex-col gap-y-6 sm:gap-x-8"
                >
                  {/* email field */}
                  <FormField
                    {...register("email", {
                      required: "Email is required",
                      pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
                    })}
                    type="email"
                    label="Email"
                    placeholder="[email protected]"
                    required
                  />
    
                  {/* password field */}
                  <FormField
                    {...register("password", {
                      required: "Email is required"
                    })}
                    type="password"
                    label="Password"
                    placeholder="*******"
                    required
                  />
    
                  <div className="flex items-center justify-between sm:col-span-2">
                    <div>
                      <input type="submit" label="Login" />
                    </div>
    
                    <div>
                      <Link href="/dashboard/auth/register">
                        <a className="underline decoration-blue-500 decoration-4 hover:decoration-2 mr-4">
                          Register
                        </a>
                      </Link>
    
                      <Link href="/dashboard/auth/forgot">
                        <a className="underline decoration-blue-500 decoration-4 hover:decoration-2">
                          Forgot your password?
                        </a>
                      </Link>
                    </div>
                  </div>
                </form>
              </div>
            </div>
          </div>
        </>
      );
    }
    
    const FormField = forwardRef(
      ({ type, name, label, required, placeholder, ...props }, ref) => {
        return (
          <div>
            <label
              htmlFor={name}
              className="block text-sm font-medium text-gray-900"
            >
              {label}
              <span className="text-red-500 font-bold text-lg">
                {required && "*"}
              </span>
            </label>
    
            <div className="mt-1">
              <input
                {...props}
                name={name}
                ref={ref}
                type={type}
                id={name}
                className={["field", `field--${type}`].join(" ")}
                placeholder={placeholder}
              />
            </div>
          </div>
        );
      }
    );
    

    Edit NextJS 10 + Tailwind 2.0 (forked)