Reactjs 使用多层嵌套对象反应钩子形式意外行为

Reactjs 使用多层嵌套对象反应钩子形式意外行为,reactjs,typescript,react-hooks,react-hook-form,Reactjs,Typescript,React Hooks,React Hook Form,最近,我从插件中发现了react钩子,乍一看,由于其出色的性能,它似乎非常适合开始使用和替换其他插件 在将插件用于一些非常简单的表单之后,我遇到了一个我希望使用插件处理的复杂表单。我的表单基于嵌套对象,该对象具有以下结构。(打字稿定义) 因此,为了处理n级嵌套表单,我创建了以下组件 const initialData = { ... datasource: { tag: "tag1", visible: true,

最近,我从插件中发现了react钩子,乍一看,由于其出色的性能,它似乎非常适合开始使用和替换其他插件

在将插件用于一些非常简单的表单之后,我遇到了一个我希望使用插件处理的复杂表单。我的表单基于嵌套对象,该对象具有以下结构。(打字稿定义)

因此,为了处理n级嵌套表单,我创建了以下组件

const initialData = {
    ...
    datasource: {
        tag: "tag1",
        visible: true,
        columns: [
            {
                property: "property",
            },
            {
                property: "property1",
            }, 
            {
                tag: "tag2",
                visible: true,
                columns: [
                    {
                        property: "property",
                    }, 
                    {
                        tag: "tag3",
                        visible: false,
                        columns: [
                            {
                                property: "property",
                            }
                        ]
                    }
                ]
            },
            {
                entity: "tag4",
                visible: false,
            }
        ],
    },
    ...
}

export const EditorContent: React.FunctionComponent<EditorContentProps> = (props: any) => {
   const form = useForm({
        mode: 'all',
        defaultValues: initialData,
    });

    return (
        <FormProvider {...form}>
                <form>
                    <Handler 
                        path="datasource"
                    />
                </form>
        </FormProvider>
    );
}
从本质上讲,如果处理程序组件应该呈现表单的嵌套对象(如datasource.columns[x]),则处理程序组件使用表单的上下文并调用自身,寄存器将使用FieldArray获取表单的列。到目前为止一切正常。我正确地呈现了完整的树(如果可以说)状对象形式

此处提供了列组件的代码以及formField辅助组件formField的代码,以供参考

export const Column: React.FunctionComponent<ColumnProps> = (props) => {
    const { fields, control, path, index, onDelete } = props;

    const value = useWatch({
        control,
        name: `${path}[${index}]`,
        defaultValue: !isNil(fields[index])? fields[index]: { 
            property: null,
        }
    });

    console.log(`Render of Column ${path} ${value.property}`);
    
    return (
        <Row>
            <Col>
                <button onClick={onDelete}>Remove Property</button>
            </Col>
            <Col>
                <FormField name={`${path}[${index}].property`} defaultValue={value.property}>
                    <Input
                        label={`Column Property name`}
                    />
                </FormField>
            </Col>
        </Row>
    );
}


export type FormFieldProps = {
    name: string;
    disabled?: boolean;
    validation?: any;
    defaultValue?: any;
    children?: any;
};

export const FormField: React.FunctionComponent<FormFieldProps> = (props) => {
    const { 
        children, 
        name, 
        validation,
        defaultValue,
        disabled,
    } = props;

    const { errors, control, setValue  } = useFormContext();

    return (
        <Controller
            name={name}
            control={control}
            defaultValue={defaultValue? defaultValue: null}
            rules={{ required: true }}
            render={props => {
                return (
                    React.cloneElement(children, { 
                        ...children.props, 
                        ...{
                            disabled: disabled || children.props.disabled,
                            value: props.value,
                            onChange: (v:any) => {
                                props.onChange(v);
                                setValue(name, v, { shouldValidate: true });
                            },
                        } 
                    })
                );
            }}
        />
    );
}


导出常量列:React.FunctionComponent=(props)=>{ const{fields,control,path,index,onDelete}=props; 常量值=useWatch({ 控制 名称:`${path}[${index}]`, defaultValue:!isNil(字段[索引])?字段[索引]:{ 属性:null, } }); log(`Render of Column${path}${value.property}`); 返回( 移除属性 ); } 导出类型FormFieldProps={ 名称:字符串; 禁用?:布尔值; 验证?:任何; 默认值?:任意; 儿童?:任何; }; export const FormField:React.FunctionComponent=(道具)=>{ 常数{ 儿童 名称 验证, 默认值, 残废 }=道具; const{errors,control,setValue}=useFormContext(); 返回( { 返回( React.cloneElement(子类,{ …儿童,道具, ...{ 残疾人:残疾人| |儿童。道具。残疾人, 价值:道具价值, onChange:(v:any)=>{ 道具更换(五); setValue(名称,v,{shouldValidate:true}); }, } }) ); }} /> ); } 问题是当我从数组中删除一个字段时。处理程序组件重新呈现
字段
数据上的正确值。但是useWatch的值具有初始数据,这会导致错误的渲染,并在窗体开始网格化时显示错误的字段

对于如何以正确的方式呈现这样的嵌套表单,有什么建议吗。我猜这不是react钩子表单的问题,但是实现中有一个错误,这似乎是导致问题的原因

...
import { get, isNil } from "lodash";
import { useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
...

export type HandlerProps = {
    path: string;
    index?: number;
    ...
}

export const Handler: React.FunctionComponent<HandlerProps> = (props) => {
    const { path, index, onDelete, ...rest } = props;

    const { control } = useFormContext();

    const name = isNil(index)? `${path}` :`${path}[${index}]`;
  
    const { fields, append, insert, remove } = useFieldArray({
        control: control,
        name: `${name}.columns`
    });

    ...

    const value = useWatch({
        control,
        name: `${name}`,
    });

    ...
    const addHandler = () => {
        append({ property: null });
    };

    console.log(`Render path ${name}`);

    return (
        <React.Fragment>
            <Row>
                <Col>
                    <FormField name={`${name}.tag`} defaultValue={value.tag}>
                        <Input
                            label={`Tag`}
                        />
                    </FormField>
                </Col>
                <Col>
                     <FormField defaultValue={value.visible} name={`${name}.visible`} >
                         <Switch />
                     </FormField>
                </Col>
                <Col>
                    <button onClick={addHandler}>Add Column Property</button>
                </Col>
            </Row>
            {
                fields && (
                    fields.map((field: any, _index: number) => {
                        if (field.property !== undefined) {
                            return (
                                <Column 
                                    path={`${name}.columns`}
                                    index={_index}
                                    control={control}
                                    fields={fields}
                                    onDelete={() => remove(_index) }
                                />
                            )
                        } else {
                            return (
                                <Handler 
                                    path={`${name}.columns`}
                                    index={_index}
                                />
                            )
                        }
                    })
                )
            }
        </React.Fragment>
    );
}
export const Column: React.FunctionComponent<ColumnProps> = (props) => {
    const { fields, control, path, index, onDelete } = props;

    const value = useWatch({
        control,
        name: `${path}[${index}]`,
        defaultValue: !isNil(fields[index])? fields[index]: { 
            property: null,
        }
    });

    console.log(`Render of Column ${path} ${value.property}`);
    
    return (
        <Row>
            <Col>
                <button onClick={onDelete}>Remove Property</button>
            </Col>
            <Col>
                <FormField name={`${path}[${index}].property`} defaultValue={value.property}>
                    <Input
                        label={`Column Property name`}
                    />
                </FormField>
            </Col>
        </Row>
    );
}


export type FormFieldProps = {
    name: string;
    disabled?: boolean;
    validation?: any;
    defaultValue?: any;
    children?: any;
};

export const FormField: React.FunctionComponent<FormFieldProps> = (props) => {
    const { 
        children, 
        name, 
        validation,
        defaultValue,
        disabled,
    } = props;

    const { errors, control, setValue  } = useFormContext();

    return (
        <Controller
            name={name}
            control={control}
            defaultValue={defaultValue? defaultValue: null}
            rules={{ required: true }}
            render={props => {
                return (
                    React.cloneElement(children, { 
                        ...children.props, 
                        ...{
                            disabled: disabled || children.props.disabled,
                            value: props.value,
                            onChange: (v:any) => {
                                props.onChange(v);
                                setValue(name, v, { shouldValidate: true });
                            },
                        } 
                    })
                );
            }}
        />
    );
}