Fluent UI 9 TextArea与useForm集成时失焦及输入异常问题求助
问题描述
- 当TextArea组件触发验证错误时会失去焦点;
- 手动调用
ref.focus()恢复焦点后,输入会忽略之前的内容,新字符从第0位开始。
相关代码
TextAreaField组件代码
import * as React from "react"; import { makeStyles, shorthands, useId, Input, Label, Field, Textarea, } from "@fluentui/react-components"; import { Controller, ControllerFieldState, ControllerRenderProps, FieldValues, UseFormStateReturn, useFormContext, } from "react-hook-form"; interface inputField { name: string; placeholder?: string; label?: string; icon?: React.ReactNode; isRequired?: boolean; } const useStyles = makeStyles({ root: { display: "flex", flexDirection: "column", ...shorthands.gap("20px"), maxWidth: "900px", width: "100%", "> div": { display: "flex", flexDirection: "column", ...shorthands.gap("3px"), }, }, }); export const TextAreaField = (props: inputField) => { const styles = useStyles(); const { control, formState, register } = useFormContext(); const err = formState.errors[props.name]?.message as string | null; console.log(formState); const inputId = useId("TextArea"); return ( <Controller key={"TextArea"} control={control} name={props.name} defaultValue={formState.defaultValues} render={function ({ field, fieldState, formState, }: { field: ControllerRenderProps<FieldValues, string>; fieldState: ControllerFieldState; formState: UseFormStateReturn<FieldValues>; }): React.ReactElement<any, string | React.JSXElementConstructor<any>> { return ( <div key={"This is Text Area"} className={styles.root}> <div key={"InnerTable"}> <Label className={props.isRequired ? "red-star" : ""} style={{ fontWeight: "bold", letterSpacing: "0.3px" }} htmlFor={props.name} > {props.label} </Label> <Field key={"Error"} validationMessage={ formState.errors[props.name]?.message as string } > <Textarea key={"NoRender"} size="medium" id={inputId} placeholder={props.placeholder} {...field} ref={(inputRef) => { if (inputRef && formState.name === props.name) { inputRef.focus(); inputRef.autofocus = true; } }} /> </Field> </div> </div> ); }} ></Controller> ); }; export default React.memo(TextAreaField);
组件配置代码
const AddAndUpdateAddressWidgets: FormField[] = [ { componentType: "TextField", order: 2, props: { errorMessage: "", name: "address_name", label: "address_name", options: [], placeholder: "", disabled: false, width: "", }, }, { componentType: "TextField", order: 3, props: { errorMessage: "", name: "pin_code", label: "Pincode", options: [], placeholder: "", disabled: false, width: "", }, }, { componentType: "TextField", order: 4, props: { errorMessage: "", name: "district_name", label: "District", options: [], placeholder: "", disabled: false, width: "", }, }, { componentType: "TextAreaField", order: 5, props: { errorMessage: "", name: "address_line_1", label: "Address Line 1", options: [], placeholder: "", disabled: false, width: "", }, }, { componentType: "TextAreaField", order: 6, props: { errorMessage: "", name: "address_line_2", label: "Address Line 2", options: [], placeholder: "", disabled: false, width: "", }, }, { componentType: "TextAreaField", order: 7, props: { errorMessage: "", name: "office_remarks", label: "office_remarks", options: [], placeholder: "", disabled: false, width: "", }, }, ]; export default AddAndUpdateAddressWidgets;
组件循环渲染代码
{sortedAddAndUpdateWidgets.map( ( field: { componentType: string | number; props: any }, index: any ) => { const Component = React.memo( componentRegistry[field.componentType] ); return ( <Component key={index} {...field.props} /> ); } )}
修复方案
问题根源
- defaultValue设置错误:Controller传入了整个表单默认值对象,而非当前字段的对应值,导致TextArea初始化值异常。
- 焦点处理逻辑不当:直接在ref回调中调用focus会触发不必要重渲染,且未保留光标位置;同时错误使用了不存在的
formState.name属性判断错误字段。 - 元素关联错误:Label的
htmlFor与Textarea的id不匹配,破坏表单元素语义关联。 - Key值冲突:多个TextAreaField使用固定key,导致React渲染异常。
修复后的TextAreaField组件
import * as React from "react"; import { makeStyles, shorthands, useId, Label, Field, Textarea, } from "@fluentui/react-components"; import { Controller, ControllerFieldState, ControllerRenderProps, FieldValues, useFormContext, } from "react-hook-form"; interface inputField { name: string; placeholder?: string; label?: string; icon?: React.ReactNode; isRequired?: boolean; } const useStyles = makeStyles({ root: { display: "flex", flexDirection: "column", ...shorthands.gap("20px"), maxWidth: "900px", width: "100%", "> div": { display: "flex", flexDirection: "column", ...shorthands.gap("3px"), }, }, }); export const TextAreaField = (props: inputField) => { const styles = useStyles(); const { control, formState } = useFormContext(); const inputId = useId("TextArea"); const textareaRef = React.useRef<HTMLTextAreaElement>(null); const isFieldInvalid = !!formState.errors[props.name]; React.useEffect(() => { // 字段验证失败时恢复焦点,并将光标定位到内容末尾 if (isFieldInvalid && textareaRef.current) { textareaRef.current.focus(); const contentLength = textareaRef.current.value.length; textareaRef.current.setSelectionRange(contentLength, contentLength); } }, [isFieldInvalid, props.name]); return ( <Controller key={props.name} control={control} name={props.name} defaultValue={formState.defaultValues?.[props.name] ?? ""} render={({ field, fieldState }) => { return ( <div className={styles.root}> <div> <Label className={props.isRequired ? "red-star" : ""} style={{ fontWeight: "bold", letterSpacing: "0.3px" }} htmlFor={inputId} > {props.label} </Label> <Field validationMessage={fieldState.error?.message}> <Textarea size="medium" id={inputId} placeholder={props.placeholder} {...field} ref={(el) => { field.ref(el); textareaRef.current = el; }} /> </Field> </div> </div> ); }} /> ); }; export default React.memo(TextAreaField);
额外优化:循环渲染的Key值
将循环渲染的key从index改为字段唯一标识field.props.name,避免组件复用导致的状态异常:
{sortedAddAndUpdateWidgets.map((field) => { const Component = componentRegistry[field.componentType]; return ( <Component key={field.props.name} {...field.props} /> ); })}
内容的提问来源于stack exchange,提问作者Aadarsh Kumar
相关产品推荐
相关产品推荐

