Storybook 无法将 register 道具传递到故事中
Storybook cannot pass register prop into stories
我有以下组件
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: ''
};
和以下故事文件
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'
};
但是,我收到的消息如下:
TypeError: register is not a function
如何将注册传递到故事中,即使我没有将它用于我的故事?
我试过传入并使用 FormProvider 并包装模板,但这似乎不起作用,除非我遗漏了什么
...
编辑 3
在与 Joris 聊天后,我现在有了以下组件
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: ''
};
现在是下一页
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="john.doe@ebrookes.dev"
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>
</>
);
}
现在的问题是表单仍然没有提交。
这里有一种方法可以满足您的需求。
const Template = (args) => {
const { register } = useForm();
return (
<form>
<FormField {...args} register={register} />
</form>
);
}
在react-hook-form,我们建议转发ref
而不是向下传递register
方法
const FormField = React.forwardRef(({
type,
label,
required,
placeholder,
validationSchema,
...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}
ref={ref}
type={type}
id={name}
name={name}
className={['field', `field--${type}`].join(' ')}
placeholder={placeholder}
/>
</div>
</div>
);
});
export default FormField;
并使用它如下:
function MyForm() {
const { register } = useForm();
return (
<form>
<FormField {...register('field-name') />
</form>
)
}
已编辑:完整的工作示例
https://codesandbox.io/s/hopeful-mahavira-pfkqs
import React from "react";
import { useForm } from "react-hook-form";
interface Props extends React.PropsWithRef<JSX.IntrinsicElements["input"]> {
label: string;
}
const FormField = React.forwardRef<HTMLInputElement, Props>(
({ label, ...props }, ref) => {
return (
<div>
<label
htmlFor={props.name}
className="block text-sm font-medium text-gray-900"
>
{label}
<span className="text-red-500 font-bold text-lg">
{props.required && "*"}
</span>
</label>
<div className="mt-1">
<input {...props} ref={ref} id={props.name} />
</div>
</div>
);
}
);
FormField.displayName = "FormField";
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
console.log(errors);
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<FormField
required
{...register("myField", { required: "Field is required" })}
label="Field example"
/>
<pre>{errors.myField?.message}</pre>
<button type="submit">Submit</button>
</form>
);
}
为了让它工作,我现在有以下组件
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: ''
};
故事
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: 'john.doe@email.com'
};
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'
};
和页面
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="john.doe@ebrookes.dev"
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>
</>
);
}
我有以下组件
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: ''
};
和以下故事文件
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'
};
但是,我收到的消息如下:
TypeError: register is not a function
如何将注册传递到故事中,即使我没有将它用于我的故事?
我试过传入并使用 FormProvider 并包装模板,但这似乎不起作用,除非我遗漏了什么
...
编辑 3
在与 Joris 聊天后,我现在有了以下组件
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: ''
};
现在是下一页
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="john.doe@ebrookes.dev"
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>
</>
);
}
现在的问题是表单仍然没有提交。
这里有一种方法可以满足您的需求。
const Template = (args) => {
const { register } = useForm();
return (
<form>
<FormField {...args} register={register} />
</form>
);
}
在react-hook-form,我们建议转发ref
而不是向下传递register
方法
const FormField = React.forwardRef(({
type,
label,
required,
placeholder,
validationSchema,
...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}
ref={ref}
type={type}
id={name}
name={name}
className={['field', `field--${type}`].join(' ')}
placeholder={placeholder}
/>
</div>
</div>
);
});
export default FormField;
并使用它如下:
function MyForm() {
const { register } = useForm();
return (
<form>
<FormField {...register('field-name') />
</form>
)
}
已编辑:完整的工作示例
https://codesandbox.io/s/hopeful-mahavira-pfkqs
import React from "react";
import { useForm } from "react-hook-form";
interface Props extends React.PropsWithRef<JSX.IntrinsicElements["input"]> {
label: string;
}
const FormField = React.forwardRef<HTMLInputElement, Props>(
({ label, ...props }, ref) => {
return (
<div>
<label
htmlFor={props.name}
className="block text-sm font-medium text-gray-900"
>
{label}
<span className="text-red-500 font-bold text-lg">
{props.required && "*"}
</span>
</label>
<div className="mt-1">
<input {...props} ref={ref} id={props.name} />
</div>
</div>
);
}
);
FormField.displayName = "FormField";
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
console.log(errors);
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<FormField
required
{...register("myField", { required: "Field is required" })}
label="Field example"
/>
<pre>{errors.myField?.message}</pre>
<button type="submit">Submit</button>
</form>
);
}
为了让它工作,我现在有以下组件
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: ''
};
故事
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: 'john.doe@email.com'
};
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'
};
和页面
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="john.doe@ebrookes.dev"
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>
</>
);
}