Search code examples
reactjsstorybookreact-hook-formreact-forms

Storybook cannot pass register prop into stories


I have the following components

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

export default function FormField({
  type,
  name,
  label,
  register,
  required,
  placeholder,
  validationSchema
}) {
  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
            {...register(name, validationSchema)}
            type={type}
            name={name}
            id={name}
            className={['field', `field--${type}`].join(' ')}
            placeholder={placeholder}
          />
        </div>
      </div>
    </>
  );
}

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 the following stories file

import FormField from './FormField';

export default {
  title: 'Forms/FormField',
  component: FormField,
  argTypes: {
    type: {
      options: ['text', 'email', 'password', 'file', 'checkbox'],
      control: {
        type: 'select'
      }
    }
  }
};

const Template = (args) => <FormField {...args} />;

export const Text = Template.bind({});
Text.args = {
  type: 'text',
  label: 'Text Field',
  name: 'text-field',
  errorMsg: 'Text field is required',
  placeholder: 'Text goes here'
};

However, the message I am getting is the following:

TypeError: register is not a function

How do I pass register into stories even if I'm not using it for my stories?

I've tried passing in and using FormProvider and wrapping the template but that didn't seem to work unless I'm missing something 🤔

...

edit 3

After chats with Joris 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.


Solution

  • To get this working 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: ''
    };
    

    stories

    import { useForm } from 'react-hook-form';
    import FormField from './FormField';
    
    export default {
      title: 'Forms/FormField',
      component: FormField,
      argTypes: {
        type: {
          options: ['text', 'email', 'password', 'file', 'checkbox'],
          control: {
            type: 'select'
          }
        }
      }
    };
    
    const Template = (args) => {
      const { register } = useForm();
    
      return <FormField {...args} register={register} />;
    };
    
    export const Text = Template.bind({});
    Text.args = {
      type: 'text',
      label: 'Text Field',
      name: 'text-field',
      errorMsg: 'Text field is required',
      placeholder: 'Text goes here'
    };
    
    export const Email = Template.bind({});
    Email.args = {
      type: 'email',
      name: 'email',
      label: 'Email',
      placeholder: '[email protected]'
    };
    
    export const Password = Template.bind({});
    Password.args = {
      type: 'password',
      name: 'password',
      label: 'Password',
      placeholder: '********'
    };
    
    export const File = Template.bind({});
    File.args = {
      type: 'file',
      name: 'file-upload',
      label: 'File upload'
    };
    
    export const Checkboxes = Template.bind({});
    Checkboxes.args = {
      type: 'checkbox',
      name: 'check-1',
      label: 'Checkboxes'
    };
    

    and 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,
          email: data.email,
          password: data.password
        });
    
        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"
                    name="email"
                    label="Email"
                    placeholder="[email protected]"
                    required
                  />
    
                  {/* password field */}
                  <FormField
                    {...register('password', {
                      required: 'Password is required'
                    })}
                    type="password"
                    name="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>
        </>
      );
    }