FromSchema: генерируем React-форму из одной строки Zod-схемы
Form.FromSchema генерирует полную форму автоматически. Четыре уровня контроля: FromTemplate → FromSchema → AutoFields → Field.* — начинаешь с автогенерации, детализируешь по мере необходимости.
15 мая 2026 г.
--TL;DR:
Кому полезно:
- Junior: научиться создавать формы в одну строку без ручной вёрстки каждого поляMiddle: освоить комбинирование AutoFields с ручными полями и паттерны FromTemplate/Builder/ConversationalModeSenior: оценить архитектуру маппера Zod type -> компонент и стратегию четырёх уровней контроля
Седьмая статья из цикла «@letar/forms — от боли к декларативным формам». Как
Идея: а что, если ноль JSX?
В предыдущих статьях мы показали, как Compound Components (
// Вся форма — одна строка
<Form.FromSchema schema={UserSchema} initialValue={data} onSubmit={save} />
Библиотека сама:
- Обходит Zod-схемуОпределяет тип каждого поляПодбирает компонент (
Как это работает
Шаг 1: обход схемы
// Упрощённая версия schema-traversal.ts
function enumerateFields(schema) {
const shape = unwrapToBaseSchema(schema)._zod?.def?.shape
if (!shape) return []
return Object.entries(shape).map(([name, fieldSchema]) => ({
name,
zodType: getZodType(fieldSchema), // string, number, boolean, ...
required: isRequired(fieldSchema),
constraints: extractConstraints(fieldSchema),
meta: getMeta(fieldSchema),
}))
}
Для схемы:
const UserSchema = z.object({
name: z
.string()
.min(2)
.meta({ ui: { title: 'Имя' } }),
email: z
.string()
.email()
.meta({ ui: { title: 'Email' } }),
role: z.enum(['admin', 'user']).meta({ ui: { title: 'Роль' } }),
age: z
.number()
.min(18)
.meta({ ui: { title: 'Возраст' } }),
bio: z
.string()
.max(500)
.optional()
.meta({ ui: { title: 'О себе' } }),
})
Получаем:
[
{ name: 'name', zodType: 'string', required: true, meta: { title: 'Имя' } }
{ name: 'email', zodType: 'string', required: true, meta: { title: 'Email' }, constraints: { inputType: 'email' } }
{ name: 'role', zodType: 'enum', required: true, meta: { title: 'Роль' }, enumValues: ['admin', 'user'] }
{ name: 'age', zodType: 'number', required: true, meta: { title: 'Возраст' } }
{ name: 'bio', zodType: 'string', required: false, meta: { title: 'О себе' }, constraints: { maxLength: 500 } }
]
Шаг 2: маппинг типов на компоненты
string → FieldString
string + email() → FieldString (type="email")
string + max > 200 → FieldTextarea
number → FieldNumber
boolean → FieldCheckbox
date → FieldDate
enum → FieldNativeSelect
array(string) → FieldTags
// Переопределение через fieldType в meta:
fieldType: 'currency' → FieldCurrency
fieldType: 'richText' → FieldRichText
fieldType: 'phone' → FieldPhone
Шаг 3: рендер
function FormFromSchema({ schema, initialValue, onSubmit, submitLabel, exclude }) {
return (
<Form schema={schema} initialValue={initialValue} onSubmit={onSubmit}>
<VStack align="stretch" gap={4}>
<FormAutoFields exclude={exclude} />
<HStack justify="flex-end">
<Form.Button.Submit>{submitLabel ?? 'Сохранить'}</Form.Button.Submit>
</HStack>
</VStack>
</Form>
)
}
Кастомизация без отказа от автогенерации
Исключение полей
<Form.FromSchema
schema={ProductSchema}
initialValue={data}
onSubmit={save}
exclude={['id', 'createdAt', 'updatedAt']} // Скрыть служебные поля
/>
AutoFields + ручные поля
Когда нужен контроль над частью формы:
<Form schema={Schema} initialValue={data} onSubmit={save}>
{/* Автоматически все поля, кроме description */}
<Form.AutoFields exclude={['description']} />
{/* description — вручную, с кастомной обёрткой */}
<Box p={4} bg="gray.50" borderRadius="md">
<Form.Field.RichText name="description" />
</Box>
<Form.Button.Submit />
</Form>
include: только нужные поля
<Form schema={UserSchema} initialValue={data} onSubmit={save}>
{/* Только name и email */}
<Form.AutoFields include={['name', 'email']} />
<Form.Button.Submit />
</Form>
Четыре уровня контроля
Уровень 1: FromSchema — ноль JSX, всё автоматически
↓ нужна кастомная вёрстка?
Уровень 2: AutoFields — автогенерация + ручные поля
↓ нужен полный контроль?
Уровень 3: Form.Field.* — Compound Components
↓ нужна императивная логика?
Уровень 4: useAppForm — хук, полный доступ к TanStack Form
Начинаете с FromSchema. Когда нужно больше контроля — спускаетесь. Не нужно переписывать — уровни совместимы.
Когда FromSchema — идеальный выбор
CRUD-формы
Модель Product с 10 полями. Нужна форма создания и редактирования:
// Создание
<Form.FromSchema
schema={ProductCreateSchema}
initialValue={emptyProduct}
onSubmit={createProduct}
submitLabel="Создать"
/>
// Редактирование
<Form.FromSchema
schema={ProductUpdateSchema}
initialValue={product}
onSubmit={updateProduct}
submitLabel="Сохранить"
exclude={['id']}
/>
Две формы — две строки.
Прототипирование
Быстро набросать форму, пока дизайн не готов:
const schema = z.object({
title: z.string().meta({ ui: { title: 'Название' } }),
price: z.number().meta({ ui: { title: 'Цена', fieldType: 'currency' } }),
category: z.enum(['A', 'B', 'C']).meta({ ui: { title: 'Категория' } }),
})
// Форма готова к тестированию
<Form.FromSchema schema={schema} initialValue={{}} onSubmit={console.log} />
Админ-панели
50 моделей × 2 формы (create + edit) = 100 форм. С FromSchema — 100 строк вместо 5000+.
Ещё больше автоматизации
Form.FromTemplate — готовые шаблоны
10 шаблонов для типичных форм. Ноль конфигурации:
// Контактная форма
<Form.FromTemplate template="contact" onSubmit={handleContact} />
// Логин
<Form.FromTemplate template="login" onSubmit={handleLogin} />
// Регистрация
<Form.FromTemplate template="register" onSubmit={handleRegister} />
Доступные шаблоны:
Каждый шаблон — готовая Zod-схема + подобранные компоненты + валидация. Можно кастомизировать через
<Form.FromTemplate template="contact" overrides={{ fields: { phone: { required: true } } }} onSubmit={handleSubmit} />
Form.Builder — JSON-конфигурация
Для динамических форм (CMS, конструкторы, no-code) — JSON-driven подход. Менеджер или продакт описывает форму в JSON, разработчик не трогает JSX:
import { FormBuilder } from '@letar/forms'
const config = {
sections: [
{
title: 'Контактные данные',
fields: [
{ name: 'name', type: 'string', label: 'Имя' },
{ name: 'email', type: 'string', label: 'Email' },
{ name: 'phone', type: 'phone', label: 'Телефон' },
],
},
{
title: 'Заказ',
fields: [
{ name: 'price', type: 'currency', label: 'Цена' },
{ name: 'category', type: 'select', label: 'Категория', options: categories },
{ name: 'quantity', type: 'number', label: 'Количество' },
],
},
],
}
<FormBuilder
config={config}
initialValue={{ name: '', email: '', phone: '' }}
onSubmit={save}
/>
Поддерживаемые типы полей:
ConversationalMode — Typeform-style
Одно поле за раз. Идеально для опросов, NPS и анкет:
import { ConversationalMode } from '@letar/forms'
<Form
schema={SurveySchema}
initialValue={{ name: '', satisfaction: 0, feedback: '' }}
onSubmit={submitSurvey}
>
<ConversationalMode
showProgress
showQuestionNumber
completedScreen={<Text>Спасибо за ответы!</Text>}
>
<Form.Field.String name="name" label="Как вас зовут?" />
<Form.Field.Rating name="satisfaction" label="Оцените сервис" />
<Form.Field.Textarea name="feedback" label="Что можно улучшить?" />
</ConversationalMode>
</Form>
Иерархия уровней контроля
Макс. автоматизация → Макс. контроль
FromTemplate → FromSchema → AutoFields → Builder → ConversationalMode → Field.*
↑ ↑ ↑ ↑ ↑ ↑
Шаблоны Вся форма Часть полей JSON-driven По одному Ручной JSX
Когда что использовать
| Сценарий | Инструмент |
|---|---|
| Типовая форма | |
| Линейная форма | |
| Частичная автогенерация | |
| CMS / динамические | |
| Опросники / анкеты | |
| Сложный layout | |
Когда FromSchema НЕ подходит
- Сложная вёрстка — двухколоночный layout, табы, аккордеоны → используйте Compound ComponentsМультистеп — FromSchema не поддерживает Steps → вручнуюУсловный рендеринг — When не работает внутри AutoFields → вручнуюНестандартный UX — кастомные анимации, inline-editing → вручную
Правило: если форма «линейная» (поля идут сверху вниз) — FromSchema. Если сложный layout — Compound Components.
Итоги
| Что | Как |
|---|---|
| Полная автогенерация | |
| Частичная автогенерация | |
| Готовые шаблоны | |
| JSON-конфигурация | |
| Typeform-style | |
| Исключение полей | |
| Включение полей | |
| Маппинг типов | Автоматический (Zod type → компонент) |
| Переопределение | |
Попробовать
- AutoFields: forms-example.letar.best/examples/auto-fieldsПродвинутое: forms-example.letar.best/examples/auto-fields-advancedИсходный код: auto-fields | auto-fields-advancedКлонировать:
В следующей статье — full-stack pipeline: от
Это седьмая статья из цикла «@letar/forms — от боли к декларативным формам». Предыдущая: Массивы и группы | Следующая: ZenStack pipeline.