如何使用 TypeScript 实现 React Hook Form 智能表单组件

Myo*_*dan 11 typescript reactjs react-hook-form

如何将 Form 的类型继承给其子项?我想通过继承类型来使用名称中的自动建议功能。并且代码运行良好。但这并不能达到我想要的效果。

我是参考下面的链接做的。作为参考,css 使用 tailwind。

https://react-hook-form.com/advanced-usage#SmartFormComponent

代码:

// Form.tsx

import { Children, createElement, FormHTMLAttributes, ReactElement } from "react";
import { FormProvider, SubmitHandler, useForm, UseFormProps } from "react-hook-form";

interface FormProps<T> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
    form: UseFormProps<T>;
    children: ReactElement | ReactElement[];
    onSubmit: SubmitHandler<T>;
}

export default function Form<T>({ children, onSubmit, form, ...rest }: FormProps<T>): JSX.Element {
    const methods = useForm<T>(form);

    return (
        <FormProvider {...methods}>
            <form onSubmit={methods.handleSubmit(onSubmit)} {...rest}>
                {Children.map(children, (child) => {
                    return child.props.name
                        ? createElement<T>(child.type, {
                                ...{
                                    key: child.props.name,
                                    ...methods.register,
                                    ...child.props
                                }
                          })
                        : child;
                })}
            </form>
        </FormProvider>
    );
}
Run Code Online (Sandbox Code Playgroud)
// Input.tsx

import { ExclamationCircleIcon } from "@heroicons/react/solid";
import { InputHTMLAttributes } from "react";
import { useFormContext } from "react-hook-form";
import { classNames } from "../../utils/classNames";

interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "id" | "className"> {
    name: string;
    label?: string;
}

export default function Input({ name, label, ...rest }: InputProps): JSX.Element {
    const methods = useFormContext();
    return (
        <>
            <div>
                <div>
                    {label && (
                        <label htmlFor={name} className="block text-sm font-medium text-gray-700">
                            {label}
                        </label>
                    )}

                    <div className="mt-1 relative">
                        <input
                            id={name}
                            {...methods.register(name)}
                            {...rest}
                            className={classNames(
                                methods.formState.errors[name]
                                    ? "border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500"
                                    : "border-gray-300 placeholder-gray-400 focus:ring-indigo-500 focus:border-indigo-500",
                                "appearance-none block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none sm:text-sm"
                            )}
                        />
                        {methods.formState.errors[name] && (
                            <div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
                                <ExclamationCircleIcon className="h-5 w-5 text-red-500" aria-hidden="true" />
                            </div>
                        )}
                    </div>
                </div>
                {methods.formState.errors[name] && (
                    <p className="mt-1 text-sm text-red-600">{methods.formState.errors[name].message}</p>
                )}
            </div>
        </>
    );
}
Run Code Online (Sandbox Code Playgroud)
// index.tsx

import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { Form, Input } from "../form";

interface FormFields {
    email: string;
    password: string;
}

const schema = yup.object().shape({
    email: yup.string().required("Email is required.").email("Email format is incorrect."),
    password: yup
        .string()
        .required("Password is required.")
        .matches(
            /^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])[A-Za-z0-9!@#$%^&*]{8,128}$/,
            "Password format is incorrect."
        )
});

export default function SignInForm(): JSX.Element {
    const onSubmit = async (data: FormFields) => {
        console.log(data);
    };

    return (
        <Form<FormFields>
            onSubmit={onSubmit}
            form={{ resolver: yupResolver(schema), reValidateMode: "onChange" }}
            className="space-y-6"
        >
            <Input name="email" type="email" label="Email address" />
            <Input name="password" type="password" label="Password" />
            <div>
                <button type="submit" className="w-full flex justify-center py-2 px-4 btn btn-indigo">
                    Sign in
                </button>
            </div>
        </Form>
    );
}
Run Code Online (Sandbox Code Playgroud)