Chakra UI Textarea无法提交至React Hook Form问题求助
问题:React Hook Form 结合 Chakra UI Textarea 无法提交表单
我正在使用 Next.js(v13)、React(v18)、Chakra UI 和 React Hook Form。仅使用 Input 组件时表单能正常提交,但把 description 字段换成 Chakra UI 的 Textarea 组件后,表单能正常渲染却无法提交,控制台也没有报错,找不到问题原因。想问下能不能通过 React Hook Form 提交 Textarea 组件的数据?
表单组件代码
import * as React from "react" import { gql } from "@apollo/client" import { Button, Stack, Textarea, Text } from "@chakra-ui/react" import { useRouter } from "next/router" import { useCreateIssueGroupMutation } from "lib/graphql" import { useForm } from "lib/hooks/useForm" import Yup from "lib/yup" import { ButtonGroup } from "./ButtonGroup" import { Form } from "./Form" import { FormError } from "./FormError" import { Input } from "./Input" import { Modal } from "antd" const _ = gql` mutation CreateIssueGroup($data: IssueGroupInput!) { createIssueGroup(data: $data) { id } } ` interface Props { onClose: () => void } const IssueGroupSchema = Yup.object().shape({ title: Yup.string().required(), description: Yup.string().required(), }) export function AdminCreateIssueGroupForm(props: Props) { const router = useRouter() const [createIssueGroup] = useCreateIssueGroupMutation() const defaultValues = { title: "", description: "", } const form = useForm({ defaultValues, schema: IssueGroupSchema }) const handleSubmit = (data: Yup.InferType<typeof IssueGroupSchema>) => { return form.handler(() => createIssueGroup({ variables: { data: { ...data } } }), { onSuccess: (res, toast) => { toast({ description: "Issue group created" }) form.reset() props.onClose() }, }) } return ( <Form {...form} onSubmit={handleSubmit}> <Stack> <Input name="title" label="Title" /> // this input works and allows me to submit the form {/* <Input name="description" label="Description" /> */} // the next 2 lines do not work. The page renders but the form does not submit <Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text> <Textarea name="description" rows={4} /> <FormError /> <ButtonGroup> <Button onClick={props.onClose}>Cancel</Button> <Button type="submit" isLoading={form.formState.isSubmitting} isDisabled={form.formState.isSubmitting} color="brand.white" fontWeight="normal" backgroundColor="brand.orange" _hover={{ backgroundColor: "brand.green", color: "brand.white", }} > Create </Button> </ButtonGroup> </Stack> </Form> ) }
Form 组件代码
import * as React from "react" import type { FieldValues, UseFormReturn } from "react-hook-form" import { FormProvider, useFormContext } from "react-hook-form" import { Box } from "@chakra-ui/react" import * as Sentry from "@sentry/nextjs" import { useToast } from "lib/hooks/useToast" interface FormContainerProps { onSubmit?: (values: any) => Promise<any> | any onBlur?: (values: any) => Promise<any> | any } const FormContainer: React.FC<FormContainerProps> = (props) => { const toast = useToast() const { handleSubmit } = useFormContext() const onSubmit = async (values: any) => { try { if (props.onBlur) { return await props.onBlur(values) } if (props.onSubmit) { return await props.onSubmit(values) } } catch (e) { console.log(e) Sentry.captureException(e) toast({ title: "Application error", description: "Something went wrong. We have been notified!", status: "error", }) return } } return ( <Box as="form" w="100%" {...(props.onSubmit && { onSubmit: handleSubmit(onSubmit) })} {...(props.onBlur && { onBlur: handleSubmit(onSubmit) })} > {props.children} </Box> ) } interface Props<T extends FieldValues> extends UseFormReturn<T>, FormContainerProps { children: React.ReactNode isDisabled?: boolean } export function Form<T extends FieldValues>({ onSubmit, onBlur, isDisabled, ...props }: Props<T>) { return ( <FormProvider {...props}> <fieldset disabled={isDisabled}> <FormContainer {...{ onSubmit, onBlur }}>{props.children}</FormContainer> </fieldset> </FormProvider> ) }
Input 组件代码
import * as React from "react" import { useFormContext } from "react-hook-form" import type { InputProps } from "@chakra-ui/react"; import { FormControl, Input as CInput } from "@chakra-ui/react" import { InputError } from "./InputError"; import { InputLabel } from "./InputLabel"; interface Props extends InputProps { name: string label?: string subLabel?: string } export const Input = ({ label, subLabel, ...props }: Props) => { const { register, formState: { errors }, } = useFormContext() const fieldError = errors?.[props.name] return ( <FormControl isInvalid={!!fieldError} isRequired={props.isRequired}> <InputLabel label={label} subLabel={subLabel} name={props.name} /> <CInput {...register(props.name)} mb={0} {...props} /> <InputError error={fieldError} /> </FormControl> ) }
问题原因与解决方法
问题出在你直接使用了 Chakra UI 的原生 Textarea 组件,但没有通过 React Hook Form 的 register 方法注册这个字段。表单无法获取到 description 的值,触发了 Yup 的必填校验却没显示错误(因为没关联表单状态),最终阻止了提交。
参考现有 Input 组件的封装方式,自己封装一个和 React Hook Form 关联的 Textarea 组件即可解决:
封装 Textarea 组件
import * as React from "react"; import { useFormContext } from "react-hook-form"; import type { TextareaProps } from "@chakra-ui/react"; import { FormControl, Textarea as CTextarea } from "@chakra-ui/react"; import { InputError } from "./InputError"; import { InputLabel } from "./InputLabel"; interface Props extends TextareaProps { name: string; label?: string; subLabel?: string; } export const Textarea = ({ label, subLabel, ...props }: Props) => { const { register, formState: { errors }, } = useFormContext(); const fieldError = errors?.[props.name]; return ( <FormControl isInvalid={!!fieldError} isRequired={props.isRequired}> <InputLabel label={label} subLabel={subLabel} name={props.name} /> <CTextarea {...register(props.name)} mb={0} {...props} /> <InputError error={fieldError} /> </FormControl> ); };
在表单中使用封装后的组件
替换原来的原生 Textarea 代码:
// 替换这两行 <Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text> <Textarea name="description" rows={4} /> // 改为: <Textarea name="description" label="Description" rows={4} />
这样处理后,Textarea 会和 React Hook Form 正确关联,表单能获取到字段值,通过校验后就能正常提交了。
内容的提问来源于stack exchange,提问作者Mel
相关产品推荐
相关产品推荐

