Перейти к основному содержимому

Хуки

ReFormer предоставляет React хуки для бесшовной интеграции с React 18+.

useFormControl

Подписка на все изменения состояния поля. Компонент перерисовывается только когда данные контрола реально изменились.

import { useFormControl } from '@reformer/core';

function TextField({ field }: { field: FieldNode<string> }) {
const { value, disabled, errors, shouldShowError } = useFormControl(field);

return (
<div>
<input
value={value}
onChange={(e) => field.setValue(e.target.value)}
onBlur={() => field.markAsTouched()}
disabled={disabled}
/>
{shouldShowError && errors.length > 0 && (
<span className="error">{errors[0].message}</span>
)}
</div>
);
}

Возвращаемое значение для FieldNode

СвойствоТипОписание
valueTТекущее значение
validbooleanВалидно
invalidbooleanЕсть ошибки
errorsValidationError[]Массив ошибок валидации
touchedbooleanПользователь взаимодействовал с полем
disabledbooleanОтключено
pendingbooleanАсинхронная валидация выполняется
shouldShowErrorbooleanПоказывать ошибку (touched + invalid)
componentPropsRecord<string, any>Пользовательские пропсы для компонента

Возвращаемое значение для ArrayNode

СвойствоТипОписание
valueT[]Текущее значение массива
lengthnumberКоличество элементов в массиве
validbooleanВалидно
invalidbooleanЕсть ошибки
errorsValidationError[]Массив ошибок валидации
touchedbooleanПользователь взаимодействовал
dirtybooleanЗначение изменено от начального
pendingbooleanАсинхронная валидация выполняется

Пример: Полный компонент поля

function FormField({ field, label }: { field: FieldNode<string>; label: string }) {
const { value, disabled, errors, shouldShowError, pending } = useFormControl(field);

return (
<div className="form-field">
<label>{label}</label>
<input
value={value}
onChange={(e) => field.setValue(e.target.value)}
onBlur={() => field.markAsTouched()}
disabled={disabled}
/>
{shouldShowError && errors.length > 0 && (
<span className="error-message">{errors[0].message}</span>
)}
{pending && <span className="loading">Проверка...</span>}
</div>
);
}

useFormControlValue

Подписка только на значение поля, без отслеживания errors, valid, touched и т.д. Используйте когда нужно только значение для условного рендеринга — это обеспечивает лучшую производительность, избегая лишних перерисовок.

import { useFormControlValue } from '@reformer/core';

function ConditionalField({
showWhenField,
field
}: {
showWhenField: FieldNode<string>;
field: FieldNode<string>;
}) {
// Перерисовывается только при изменении showWhenField.value
const showWhenValue = useFormControlValue(showWhenField);

if (showWhenValue !== 'show') {
return null;
}

return <TextField field={field} />;
}

Возвращаемое значение

ТипОписание
TТекущее значение

Когда использовать

Используйте useFormControlValue вместо useFormControl когда:

  • Вам нужно только значение для условного рендеринга
  • Вы хотите минимизировать перерисовки
  • Вам не нужно состояние валидации или другие свойства
// ❌ Неэффективно - перерисовывается при любом изменении состояния
function BadExample({ field }: { field: FieldNode<string> }) {
const { value } = useFormControl(field);
return <span>Выбрано: {value}</span>;
}

// ✅ Эффективно - перерисовывается только при изменении значения
function GoodExample({ field }: { field: FieldNode<string> }) {
const value = useFormControlValue(field);
return <span>Выбрано: {value}</span>;
}

Примеры использования

С GroupNode

Доступ к контролам из GroupNode:

function UserForm() {
const form = useMemo(() => createUserForm(), []);

return (
<form>
<FormField field={form.controls.firstName} label="Имя" />
<FormField field={form.controls.lastName} label="Фамилия" />
<FormField field={form.controls.email} label="Email" />
</form>
);
}

С ArrayNode

Рендеринг динамических массивов:

function PhoneList({ array }: { array: ArrayNode<PhoneSchema> }) {
const { length } = useFormControl(array);

return (
<div>
{array.map((phone, index) => (
<div key={phone.id}>
<FormField field={phone.controls.type} label="Тип" />
<FormField field={phone.controls.number} label="Номер" />
<button onClick={() => array.removeAt(index)}>Удалить</button>
</div>
))}
{length === 0 && <span>Телефоны не добавлены</span>}
<button onClick={() => array.push({ type: 'mobile', number: '' })}>Добавить телефон</button>
</div>
);
}

Отправка формы

Обработка отправки формы:

function ContactForm() {
const form = useMemo(() => createContactForm(), []);
const { invalid } = useFormControl(form.controls.email);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
form.markAsTouched();

if (form.valid) {
console.log(form.value);
}
};

return (
<form onSubmit={handleSubmit}>
<FormField field={form.controls.name} label="Имя" />
<FormField field={form.controls.email} label="Email" />
<button type="submit" disabled={invalid}>
Отправить
</button>
</form>
);
}

Производительность

Оба хука используют useSyncExternalStore для оптимальной интеграции с React 18+:

function Form() {
// Этот компонент НЕ перерисовывается при изменении полей
return (
<form>
<NameField /> {/* Перерисовывается только при изменении name */}
<EmailField /> {/* Перерисовывается только при изменении email */}
</form>
);
}

Следующие шаги