Демо этого проекта находится здесь

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

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

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

В этой статье мы рассмотрим только проверки на стороне клиента.

Существует несколько способов реализовать проверки на стороне клиента. В этом руководстве мы будем использовать Tailwind CSS, который является отличным внешним интерфейсом для CSS.

1. Создайте свой проект

Откройте свой терминал и создайте новый проект с помощью Vite. Я назвал его tailwind-form-validations, но вы можете дать любое другое имя. Кроме того, мы передаем флаг --template react, поскольку он говорит Vite создать проект React.

npm create vite@latest tailwind-form-validations -- --template react

После создания проекта перейдите к нему с помощью следующей команды:

cd tailwind-form-validations

Затем установите Tailwind CSS с помощью этой команды:

npm install -D tailwindcss postcss autoprefixer

Затем создайте файл конфигурации Tailwind:

npx tailwindcss init -p

Затем замените раздел содержимого в файле tailwind.config.js следующим кодом:

content: [
  "./index.html",
  "./src/**/*.{js,ts,jsx,tsx}",
],

Наконец, добавьте директивы Tailwind в ваш файл index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Кроме того, я добавил дополнительные пакеты для иконок и CSS-форм Tailwind. Вы также можете установить их, если хотите. Просто скопируйте и вставьте следующие команды в свой терминал:

npm install react-icons --save
npm install -D @tailwindcss/forms

Поскольку вы установили CSS-формы Tailwind, обязательно добавьте их в файл конфигурации Tailwind. Добавьте его в раздел плагинов конфигурационного файла Tailwind следующим образом:

plugins: [
    require('@tailwindcss/forms'),
    // ...
],

2. Создайте компоненты и файл данных

Создайте компонент Button.jsx в каталоге src/components и вставьте в этот файл следующий код:

const Button = ({ title, icon, ariaLabel }) => {
    return (
        <button
            aria-label={ariaLabel}
            type="button"
            className="flex w-full items-center justify-center space-x-4 rounded-md border border-gray-300 bg-gray-100 py-3 hover:border-purple-400 hover:bg-gray-200 focus:ring-2 focus:ring-purple-400 focus:ring-offset-1 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-purple-600 dark:hover:bg-gray-600"
        >
            {icon}
            <p>{title}</p>
        </button>
    );
};

export default Button;

Мы создали этот компонент Button, так как будем повторно использовать его в нашей форме.

Затем создайте файл countries.js в каталоге src/data и вставьте туда следующий код:

export const countries = [
    { name: 'Afghanistan', code: 'AF' },
    { name: 'Åland Islands', code: 'AX' },
    { name: 'Albania', code: 'AL' },
    { name: 'Algeria', code: 'DZ' },
    { name: 'American Samoa', code: 'AS' },
    { name: 'AndorrA', code: 'AD' },
    { name: 'Angola', code: 'AO' }
    ..............
    ..............
];

Файл countries.js — это длинный файл, содержащий список всех стран мира. Я только что добавил первые несколько стран в наш код, но вы можете получить полный список стран в формате JSON здесь и вставить его в свой файл countries.js.

3. Форма проверки

Замените код в файле App.jsx следующим кодом:

import { useState } from 'react';
import { FiGithub, FiTwitter } from 'react-icons/fi';
import Button from './components/Button';
import { countries } from './data/countries';

function App() {
    const [data, setData] = useState({
        fullName: '',
        email: '',
        country: '',
        password: ''
    });

    const handleRegistration = (e) => {
        e.preventDefault();

        console.log(data);
    };

    // Destructure data
    const { ...allData } = data;

    // Disable submit button until all fields are filled in
    const canSubmit = [...Object.values(allData)].every(Boolean);

    return (
        <div className="flex min-h-screen items-center justify-center px-4">
            <div className="flex w-full flex-col items-center py-10 sm:justify-center">
                <div className="w-full max-w-sm rounded-md  bg-white px-6 py-6 shadow-md dark:bg-gray-900 sm:rounded-lg">
                    <form
                        action=""
                        onSubmit={handleRegistration}
                        className="group"
                    >
                        <div>
                            <label
                                htmlFor="fullName"
                                className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                            >
                                Full Name
                            </label>
                            <div className="flex flex-col items-start">
                                <input
                                    type="text"
                                    name="fullName"
                                    placeholder="Full Name"
                                    className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
                                    pattern="[0-9a-zA-Z ]{6,}"
                                    required
                                    onChange={(e) => {
                                        setData({
                                            ...data,
                                            fullName: e.target.value
                                        });
                                    }}
                                />
                                <span className="mt-1 hidden text-sm text-red-400">
                                    Full name must be at least 6 characters long
                                </span>
                            </div>
                        </div>
                        <div className="mt-4">
                            <label
                                htmlFor="email"
                                className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                            >
                                Email
                            </label>
                            <div className="flex flex-col items-start">
                                <input
                                    type="email"
                                    name="email"
                                    placeholder="Email"
                                    className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
                                    autoComplete="off"
                                    required
                                    pattern="[a-z0-9._+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
                                    onChange={(e) => {
                                        setData({
                                            ...data,
                                            email: e.target.value
                                        });
                                    }}
                                />
                                <span className="mt-1 hidden text-sm text-red-400">
                                    Please enter a valid email address.{' '}
                                </span>
                            </div>
                        </div>
                        <div className="mt-4">
                            <label
                                htmlFor="country"
                                className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                            >
                                Select your country
                            </label>
                            <select
                                id="country"
                                className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500"
                                onChange={(e) => {
                                    setData({
                                        ...data,
                                        country: e.target.value
                                    });
                                }}
                            >
                                {countries.map((country) => (
                                    <option
                                        key={country.code}
                                        value={country.code}
                                    >
                                        {country.name}
                                    </option>
                                ))}
                            </select>
                        </div>

                        <div className="mt-4">
                            <label
                                htmlFor="password"
                                className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                            >
                                Password
                            </label>
                            <div className="flex flex-col items-start">
                                <input
                                    type="password"
                                    name="password"
                                    placeholder="Password"
                                    className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
                                    autoComplete="off"
                                    required
                                    pattern="[0-9a-zA-Z]{8,}"
                                    onChange={(e) => {
                                        setData({
                                            ...data,
                                            password: e.target.value
                                        });
                                    }}
                                />
                                <span className="mt-1 hidden text-sm text-red-400">
                                    Password must be at least 8 characters.{' '}
                                </span>
                            </div>
                        </div>
                        <div className="mt-4">
                            <label
                                htmlFor="password_confirmation"
                                className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                            >
                                Confirm Password
                            </label>
                            <div className="flex flex-col items-start">
                                <input
                                    type="password"
                                    name="password_confirmation"
                                    placeholder="Confirm password"
                                    className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
                                    autoComplete="off"
                                    required
                                    pattern="[0-9a-zA-Z]{8,}"
                                    onChange={(e) => {
                                        setData({
                                            ...data,
                                            password: e.target.value
                                        });
                                    }}
                                />
                                <span className="mt-1 hidden text-sm text-red-400">
                                    Password must be at least 8 characters.{' '}
                                </span>
                            </div>
                        </div>
                        <a
                            href="#"
                            className="pt-1 text-xs text-purple-600 hover:text-purple-800 hover:underline dark:text-purple-300 dark:hover:text-purple-100"
                        >
                            Forget Password?
                        </a>
                        <div className="mt-4 flex items-center">
                            <button
                                type="submit"
                                disabled={!canSubmit}
                                className="mt-2 w-full rounded-lg bg-purple-700 px-5 py-3 text-center text-sm font-medium text-white hover:bg-purple-600 focus:outline-none focus:ring-1 focus:ring-blue-300 disabled:cursor-not-allowed disabled:bg-gradient-to-br disabled:from-gray-100 disabled:to-gray-300 disabled:text-gray-400 group-invalid:pointer-events-none group-invalid:bg-gradient-to-br group-invalid:from-gray-100 group-invalid:to-gray-300 group-invalid:text-gray-400 group-invalid:opacity-70"
                            >
                                Create account
                            </button>
                        </div>
                    </form>
                    <div className="text-md mt-4 text-zinc-600 dark:text-zinc-300">
                        Already have an account?{' '}
                        <span>
                            <a
                                className="text-purple-600 hover:text-purple-800 hover:underline dark:text-purple-400 dark:hover:text-purple-100"
                                href="#"
                            >
                                Login instead
                            </a>
                        </span>
                    </div>
                    <div className="my-4 flex w-full items-center">
                        <hr className="my-8 h-px w-full border-0 bg-gray-200 dark:bg-gray-700" />
                        <p className="px-3 ">OR</p>
                        <hr className="my-8 h-px w-full border-0 bg-gray-200 dark:bg-gray-700" />
                    </div>
                    <div className="my-6 space-y-2">
                        <Button
                            title="Continue with Github"
                            ariaLabel="Continue with Github"
                            icon={<FiGithub className="text-xl" />}
                        />
                        <Button
                            title="Continue with Twitter"
                            ariaLabel="Continue with Twitter"
                            icon={<FiTwitter className="text-xl" />}
                        />
                    </div>
                </div>
                <div className="mt-6 flex items-center justify-center ">
                    <a
                        href="https://github.com/realstoman/tailwind-form-validations"
                        target="__blank"
                        className="cursor-pointer text-xl text-gray-700 underline hover:text-gray-900"
                    >
                        Github repo
                    </a>
                </div>
            </div>
        </div>
    );
}

export default App;

Это фактическая форма, которая имеет правила проверки в полях ввода. Ниже приведено краткое описание файла:

  • Мы создаем состояние данных, которое будет содержать значения формы.
  • Мы деструктурируем все данные в allData, используя синтаксис распространения.
  • Затем мы создаем переменную canSubmit, которая обеспечивает заполнение всех полей перед отправкой.
  • Каждое поле ввода имеет необходимые классы проверки из Tailwind CSS, метод onChange, который сохраняет обновленный ввод или выбирает значение, а также шаблон, построенный с использованием регулярного выражения.
  • Затем мы передаем !canSubmit, который переключит состояние формы на отключенное свойство в кнопке.
  • И последнее, но не менее важное: мы используем компонент кнопки, который мы создали, чтобы продолжить работу с Github и Twitter.

Совет. Вы можете извлечь поля ввода и выбрать поля, чтобы разделить повторно используемые компоненты, чтобы уменьшить размер файла App.jsx.

Исходный код для этого руководства доступен в моей учетной записи Github здесь: https://github.com/realstoman/tailwind-form-validations.

Заключение

В этой статье рассмотрено следующее:

  • Создание проекта React с помощью Vite
  • Добавление CSS Tailwind в React
  • Создание повторно используемых компонентов
  • Проверка форм с помощью Tailwind CSS