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