Используя Remix, Remix Validated Form, Zod и Zod Form Data, разработчики могут проверять формы, легко обрабатывать и отображать состояния ошибок, предотвращать ложные отправки, улучшать опыт разработчиков и увеличивать скорость разработки.

Remix использует преимущества API-интерфейсов веб-платформы, что упрощает создание форм, которые отправляют данные в действие, которое передает данные формы на сервер.

Zod предоставляет способ проверки данных как на стороне клиента, так и на стороне сервера, обеспечивая точность и безопасность при обработке пользовательских данных. Данные формы Zod еще больше упрощают процесс, предоставляя удобный способ сопоставления данных из формы с типом Zod.

Вы найдете исходный код и ссылку на все пакеты внизу поста. Еще одна замечательная особенность этой комбинации заключается в том, что многие функции по-прежнему работают, даже если вы отключили JavaScript, поскольку проверка выполняется на стороне клиента и сервера.

Zod — это библиотека JavaScript, которая предоставляет мощный, типобезопасный способ определения, проверки и управления данными. Он использует декларативный синтаксис, похожий на TypeScript, что упрощает создание типовых форм с помощью React.

Библиотека предоставляет способ проверки данных как на стороне клиента, так и на стороне сервера, обеспечивая точность и безопасность при обработке пользовательских данных. Данные формы Zod еще больше упрощают процесс, предоставляя удобный способ сопоставления данных из формы с типом Zod.

Первым шагом в создании формы с типобезопасностью является создание компонента кнопки отправки. Эта кнопка отправки может обрабатываться автоматически с помощью useIsSubmittingHook из remix-validated-form. Этот хук позволит форме автоматически обрабатывать статус отправки формы, чтобы предотвратить двойную отправку.

import { useIsSubmitting } from "remix-validated-form";

export const SubmitButton = ({
    submitText = "Submit",
  }: {
    submitText?: string;
  }) => {
    const isSubmitting = useIsSubmitting();
  
    return (
      <button
        type="submit"
        disabled={isSubmitting}
        className="bg-black text-white p-3 rounded-md"
      >
        {isSubmitting ? "Submitting..." : submitText}
      </button>
    );
  };

Далее нам нужно создать поле ввода, которое использует хук useField из remix-validated-form. Этот хук имеет несколько полезных функций, таких как обратная связь об ошибках. Мы можем использовать это для отображения визуальной обратной связи о том, какие поля дают нам ошибки, а также для отображения этой ошибки пользователю. Кроме того, мы используем функцию clearError при нажатии на ввод, чтобы сбросить ошибку.

import classNames from "classnames";
import { useField } from "remix-validated-form";

export const Input = ({
  name,
  title,
  id,
}: {
  name: string;
  title?: string;
  id?: string;
}) => {
  const field = useField(name);
  return (
    <div className={"flex flex-col w-full"}>
      <label htmlFor={name}>{title}</label>
      <input
        {...field.getInputProps()}
        className={classNames("border-2 rounded-md", {
          "border-2 !border-red-500": field.error,
        })}
        name={name}
        id={id ? id : name}
        onClick={() => {
          field.clearError();
        }}
        onChange={() => {
          if (field.error) field.clearError();
        }}
      />
      <div className="text-red-500">{field.error}</div>
    </div>
  );
};

В приведенном ниже коде мы создаем схему, мы создаем валидатор на основе этой схемы, затем мы создаем форму на стороне клиента, в которую мы передаем схему, затем форма проверяет форму как на клиенте, так и на сервере против этого валидатора. .

Затем, как только мы отправим эти данные формы на сервер, он проверит эти данные с помощью валидатора, а затем мы сможем получить проверенные данные.

import { ActionArgs } from "@remix-run/node";
import { withZod } from "@remix-validated-form/with-zod";
import { ValidatedForm, validationError } from "remix-validated-form";
import { z } from "zod";
import { zfd } from "zod-form-data";
import { Input } from "~/components/input";
import { SubmitButton } from "~/components/submit-button";

const createPostSchema = zfd.formData({
  //  zfd(zod form data) is a helper that helps to parse the form data to an object
  // using the zod schema, if there are multiple values with the same name an array will be returned.
  // it can handle URLSearchParams, FormData, and plain objects
  title: zfd.text(z.string().min(1).max(100)),
  author: zfd.text(z.string().min(1).max(50)),
  content: zfd.text(z.string().min(1).max(1000)),
  published: zfd.checkbox(),
});
export type CreatePostType = z.infer<typeof createPostSchema>;
// remix-validated-form with-zod is a helper that helps to validate form data
// remix-validated-form supported custom validation and other libraries like yup
const createPostValidator = withZod(createPostSchema);
export async function action({ request }: ActionArgs) {
  const formData = await request.formData();
  const validation = await createPostValidator.validate(formData);
  // if there are any errors, return validationError, this is also handled
  // by remix-validated-form
  if (validation.error) {
    return validationError(validation.error);
  }
  // if we make it here, we know that there are no errors so we can
  // get the data from the validation object
  const { title, content, author, published } = validation.data;
  console.log("Creating Post...", { title, content, author, published });
}
export default function () {
  return (
    <div className="flex items-center justify-center">
      {/* Validated form will validate form on both the server side and client side 
      form will not submit to server if there are any errors.*/}
      <ValidatedForm
        validator={createPostValidator}
        className="flex flex-col space-y-4 w-10/12 lg:w-1/2"
        method="post"
      >
        <Input name="title" title="Post Title" />
        <Input name="author" title="Author" />
        <Input name="content" title="Post Content" />
        <div className="flex flex-row items-center">
          <label htmlFor="publish">Publish</label>
          <input
            type="checkbox"
            id="publish"
            name="publish"
            className="ml-2 h-5 w-5"
          />
        </div>
        <div className="w-full flex justify-center items-center">
          <SubmitButton submitText="Create Post" />
        </div>
      </ValidatedForm>
    </div>
  );
}

Вот как выглядит наша форма:

На скриншоте ниже я отправил форму без заполнения некоторых полей. Вы заметите, что у поля автора есть стили фокуса, хорошая вещь в remix-validated-form заключается в том, что он автоматически фокусируется на первом поле, в котором возникла ошибка. Когда вы нажмете на поле или начнете печатать, вы заметите, что ошибки устранены.

Разрушая это

Давайте разберем каждую часть из файла выше, мы можем разбить его на 3 части в том порядке, в котором все происходит:

  • Валидатор формы/зода
  • Форма, которая будет отправлена ​​на сервер
  • Затем действие на стороне сервера, которое будет обрабатывать данные формы

Библиотека zod-form-data предоставляет вспомогательные средства проверки для Zod специально для разбора FormData или URLSearchParams, что особенно полезно при использовании remix и remix-validated-form. Это упрощает процесс проверки данных формы, позволяя пользователям писать свои типы ближе к тому, как они хотят.

const createPostSchema = zfd.formData({
  title: zfd.text(z.string().min(1).max(100)),
  author: zfd.text(z.string().min(1).max(50)),
  content: zfd.text(z.string().min(1).max(1000)),
  published: zfd.checkbox(),
});

const createPostValidator = withZod(createPostSchema);

Все, что нам нужно сделать, это использовать ValidatedForm из библиотеки remix-validated-form. Функционально он очень похож на компонент Remix Form с добавлением валидатора, конечно, под капотом происходит волшебство, и я бы посоветовал вам прочитать их документы.

Мы также используем несколько компонентов Input, которые мы сделали, включая обработку ошибок, а также флажок и компонент SubmitButton. Когда пользователь заполняет форму, сначала при отправке форма будет проверена на клиенте, если это не удается, мы увидим состояния ошибки во входных данных, однако, если это удается, данные формы передаются на сервер, где Затем сервер проверит данные формы, используя ту же схему проверки.

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

Лучшая часть использования Remix, который использует серверную часть и Remix Validated Form, выполняющую проверку на стороне клиента и сервера, даже если вы отключите JavaScript, мы все равно увидим ошибку полей при отправке, потому что действие вернет ошибки, а Remix SSRs страница с данными действия! Конечно, когда JavaScript включен, Remix не нужно перезагружать всю страницу, чтобы получить тот же результат.

export default function () {
  return (
    <div className="flex items-center justify-center
      <ValidatedForm
        validator={createPostValidator}
        className="flex flex-col space-y-4 w-10/12 lg:w-1/2"
        method="post"
      >
        <Input name="title" title="Post Title" />

        <Input name="author" title="Author" />

        <Input name="content" title="Post Content" />

        <div className="flex flex-row items-center">
          <label htmlFor="publish">Publish</label>
          <input
            type="checkbox"
            id="publish"
            name="publish"
            className="ml-2 h-5 w-5"
          />
        </div>

        <div className="w-full flex justify-center items-center">
          <SubmitButton submitText="Create Post" />
        </div>
      </ValidatedForm>
    </div>
  );
}

Когда форма отправляется, действие выполняется на стороне сервера, здесь мы получаем данные формы из запроса. Мы передаем эти данные формы валидатору, который проверяет каждое поле на соответствие проверке для каждого поля, которое мы определяем. Если есть ошибка, мы отвечаем с помощью validationError, и это будет обработано на стороне клиента. Если мы пройдем этап ошибки, мы можем быть уверены, что у нас есть достоверные данные. Здесь я просто деструктурирую данные, чтобы продемонстрировать, что у нас действительно есть достоверные данные без ошибок типа, и консоль записывает эти данные. Отсюда, как правило, вы захотите затем вставить эти данные в свою базу данных или любой другой вариант использования, который у вас может быть.

export async function action({ request }: ActionArgs) {
  const formData = await request.formData();

  const validation = await createPostValidator.validate(formData);

  if (validation.error) {
    return validationError(validation.error);
  }

  const { title, content, author, published } = validation.data;

  console.log("Creating Post...", { title, content, author, published });
}

Ниже мы используем функцию Zod infer, это невероятно полезная утилита, например, если вы хотите создать функцию, в которую вы вставляете данные в свою базу данных, вы можете вывести типы входных данных для функции только из схемы формы. Ниже приведен пример CreatePostType

const createPostSchema = zfd.formData({
  title: zfd.text(z.string().min(1).max(100)),
  author: zfd.text(z.string().min(1).max(50)),
  content: zfd.text(z.string().min(1).max(1000)),
  published: zfd.checkbox(),
});

Результирующий тип:

Правильные формы и обеспечение положительного пользовательского опыта всегда являются проблемой, я играю с Zod и этими библиотеками уже несколько месяцев, и я не нашел более простого типа, проверки и надежного решения, чем эти библиотеки. Я также уверен, что едва касаюсь поверхности того, что вы можете делать с этими библиотеками.

Привет пакетам, которые я использовал, пожалуйста, проверьте их и прочитайте их документы:

Исходный код