#reactjs #typescript #react-hooks #react-hook-form
#reactjs #typescript #реагирование-перехваты #react-hook-form
Вопрос:
Недавно я обнаружил плагин react-hook-from, который на первый взгляд кажется идеальным для начала использования и замены других плагинов из-за его высокой производительности.
После использования плагина для некоторых действительно простых форм я наткнулся на сложную форму, которую я хочу обработать с помощью плагина. Моя форма основана на вложенном объекте, который имеет следующую структуру. (Определение Typescript)
type model = {
tag: string;
visible: boolean;
columns?: (modelColumn | model)[];
}
type modelColumn = {
property: string;
}
Итак, для обработки 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>
);
}
В вышеупомянутом компоненте создается основная форма, загруженная исходными данными. (Я привел примерное значение). Handler
Компонент имеет логин рекурсии со свойством path для вызова вложенной логики типа данных form. Вот пример реализации.
...
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 amp;amp; (
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>
);
}
По сути, компонент-обработчик использует контекст формы и вызывает сам себя, если он должен отображать вложенный объект формы, такой как datasource.columns[x], который регистрируется в useFieldArray для получения его столбцов. Пока все работает нормально. Я правильно отображаю полное дерево (если можно так выразиться), как форму объекта.
Для справки здесь приведен код компонента Column, а также для вспомогательного компонента 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 });
},
}
})
);
}}
/>
);
}
Проблема заключается в том, когда я удаляю поле из массива. Компонент обработчика повторно отображает fields
данные с правильными значениями. Но значения useWatch содержат исходные данные, что приводит к неправильному отображению с отображением неправильных полей, и формы начинают объединяться.
Есть ли какие-либо советы о том, как правильно отобразить такую вложенную форму. Я предполагаю, что это не проблема с формой react-hook, но в реализации есть ошибка, которая, по-видимому, вызывает проблему.