Modular Forms

Modular Forms - это типобезопасная библиотека форм, построенная на базе Qwik. Headless-дизайн даёт вам полный контроль над внешним видом вашей формы. Библиотека заботится об управлении состоянием и проверке ввода.

Чтобы начать работу, установите пакет npm:

npm install @modular-forms/qwik

Определите вашу форму

Прежде чем приступить к созданию формы, вы определяете структуру и типы данных полей. Кроме строк Modular Forms может работать с логическими типами, числами, файлами, датами, объектами и массивами.

type LoginForm = {
  email: string;
  password: string;
};

Поскольку Modular Forms поддерживает Valibot и Zod для валидации ввода, вы можете получить определение типа из схемы.

import { email, type Input, minLength, object, string } from 'valibot';
 
const LoginSchema = object({
  email: string([
    minLength(1, 'Пожалуйста, введите свой e-mail.'),
    email('Адрес электронной почты неверно отформатирован.'),
  ]),
  password: string([
    minLength(1, 'Пожалуйста, введите пароль.'),
    minLength(8, 'Ваш пароль должен состоять из 8 или более символов.'),
  ]),
});
 
type LoginForm = Input<typeof LoginSchema>;

Если вам интересно, почему в этом руководстве предпочтение отдается Valibot, а не Zod, рекомендую прочитать вот этот анонсирующий пост.

Установите начальные значения

После того как вы создали определение типа, продолжите работу с начальными значениями вашей формы. Для этого создайте routeLoader$ с использованием в качестве дженерика ваш ранее созданный тип.

export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
  email: '',
  password: '',
}));

Вместо пустых строк, в routeLoader$ вы можете запрашивать и передавать значения из вашей базы данных. На основе переданного объекта будет инициализировано хранилище вашей формы, чтобы Qwik мог надежно пререндерить ваш сайт на сервере. Начальные значения также используются для проверки, изменилось ли значение поля после пользовательского ввода.

Создайте форму

Чтобы создать форму, вы используете хук useForm. Он возвращает хранилище вашей формы и объект с компонентами Form, Field и FieldArray. В качестве параметра вы передаете объект useForm, с ранее созданным загрузчиком.

export default component$(() => {
  const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
    loader: useFormLoader(),
  });
});

Вы можете использовать объект loginForm для доступа к текущему состоянию вашей формы. Кроме того, вы можете передать его в различные методы, предоставляемые библиотекой, такие как reset или setValue, чтобы вручную изменить состояние.

В JSX-части вашего компонента вы продолжаете компонент Form. Он окружает поля вашей формы, и через его свойства вы можете определить, что произойдет, когда форма будет отправлена.

export default component$(() => {
  const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
    loader: useFormLoader(),
  });
 
  return <Form></Form>;
});

Добавьте поля формы

Теперь вы можете продолжить работу с полями вашей формы. С компонентами Field и FieldArray вы регистрируете поле или множество полей. Оба компонента являются автономными и предоставляют вам прямой доступ к их текущему состоянию. Второй параметр свойства рендера должен быть передан элементу <input />, <select /> или <textarea />, чтобы связать его с вашей формой.

<Form>
  <Field name="email">
    {(field, props) => (
      <input {...props} type="email" value={field.value} />
    )}
  </Field>
  <Field name="password">
    {(field, props) => (
      <input {...props} type="password" value={field.value} />
    )}
  </Field>
  <button type="submit">Войти</button>
</Form>

Такая конструкция API приводит к полностью типобезопасной форме. Кроме того, она даёт вам полный контроль над пользовательским интерфейсом. Вы можете разработать собственный компонент TextInput или подключить библиотеку готовых компонентов.

Валидация значений

Одной из основных функций Modular Forms является валидация ввода. Для этого можно использовать схему Valibot или Zod, или наши внутренние функции валидации. Чтобы упростить это руководство, мы используем схему Valibot, которую мы создали ранее, и передадим ее в хук useForm.

valiForm$ - это адаптер, преобразующий сообщения об ошибках Valibot в формат, ожидаемый Modular Forms. Для Zod вместо этого используйте zodForm$.

const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
  loader: useFormLoader(),
  validate: valiForm$(LoginSchema),
});

Теперь вам нужно только отобразить сообщения об ошибках ваших полей в случае их возникновения.

<Field name="email">
  {(field, props) => (
    <div>
      <input {...props} type="email" value={field.value} />
      {field.error && <div>{field.error}</div>}
    </div>
  )}
</Field>

Обработка отправки

На последнем этапе вам остаётся только получить доступ к значениям через функцию при отправке формы, чтобы обработать и использовать их в дальнейшем. Для этого можно использовать formAction$ или свойство onSubmit$ компонента Form.

export const useFormAction = formAction$<LoginForm>((values) => {
  // Запускается на сервере
}, valiForm$(LoginSchema));
 
export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: valiForm$(LoginSchema),
  });
 
  const handleSubmit = $<SubmitHandler<LoginForm>>((values, event) => {
    // Запускается на клиенте
  });
 
  return (
    <Form onSubmit$={handleSubmit}>

    </Form>
  );
});

Окончательная форма

Если мы теперь соберём все строительные блоки вместе, то получим рабочую форму входа в систему. Ниже вы можете увидеть собранный код и опробовать его в прилагаемой песочнице.

// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$, type QRL } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import type { InitialValues, SubmitHandler } from '@modular-forms/qwik';
import { formAction$, useForm, valiForm$ } from '@modular-forms/qwik';
import { email, type Input, minLength, object, string } from 'valibot';
 
const LoginSchema = object({
  email: string([
    minLength(1, 'Пожалуйста, введите свой e-mail.'),
    email('Адрес электронной почты неверно отформатирован.'),
  ]),
  password: string([
    minLength(1, 'Пожалуйста, введите пароль.'),
    minLength(8, 'Ваш пароль должен состоять из 8 или более символов.'),
  ]),
});
 
type LoginForm = Input<typeof LoginSchema>;
 
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
  email: '',
  password: '',
}));
 
export const useFormAction = formAction$<LoginForm>((values) => {
  // Запускается на сервере
}, valiForm$(LoginSchema));
 
export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: valiForm$(LoginSchema),
  });
 
  const handleSubmit: QRL<SubmitHandler<LoginForm>> = $((values, event) => {
    // Запускается на клиенте
    console.log(values);
  });
 
  return (
    <Form onSubmit$={handleSubmit}>
      <Field name="email">
        {(field, props) => (
          <div>
            <input {...props} type="email" value={field.value} />
            {field.error && <div>{field.error}</div>}
          </div>
        )}
      </Field>
      <Field name="password">
        {(field, props) => (
          <div>
            <input {...props} type="password" value={field.value} />
            {field.error && <div>{field.error}</div>}
          </div>
        )}
      </Field>
      <button type="submit">Войти</button>
    </Form>
  );
});

Резюме

Вы изучили основы Modular Forms и готовы создать свою первую простую форму. Для получения дополнительной информации вы можете найти другие руководства и справочник API на нашем сайте: modularforms.dev.

Вам нравится Modular Forms? Для нас было бы большой честью получить от вас звезду на GitHub!

Участники

Спасибо всем участникам, которые помогли сделать эту документацию лучше!

  • fabian-hiller
  • igorbabko
  • RaeesBhatti
  • uceumice
  • Benny-Nottonson
  • mrhoodz