Формы - это, пожалуй, одна из наиболее часто используемых функций для большинства веб-приложений, но, возможно, одна из самых неприятных вещей для разработчиков. Большинству сайтов требуется множество различных форм, которые требуют ввода, многократно используемого во многих местах. Сама проверка чрезвычайно важна, чтобы вы не передавали ненужные значения из форм в серверную часть. Кроме того, с точки зрения пользователя, сообщения, которые вы предоставляете, и то, как вы их показываете, могут иметь огромное значение для общего пользовательского опыта и уменьшить разочарование при заполнении длинных и сложных форм.
Уже существует множество библиотек для форм, чтобы упростить этот процесс. Двумя из самых больших, на которые я смотрел, прежде чем я решил реализовать свои собственные хуки, были redux-form и formik. Хотя оба казались хорошими альтернативами, я просто погрузил пальцы ног в крючки и хотел изучить все тонкости крючков. В конце концов, почему бы не пойти родным, если можно?
В игру вступают хуки, вероятно, один из самых важных инструментов, которые я использовал для реагирования. Хотя я по-прежнему большой любитель redux и хотел бы использовать для гораздо более сложного API, где данные распределяются в компонентах, широко распространенных по всему приложению, моя компания создает сайт, который намного проще с точки зрения отказа от обмена данными из многих компонентов. кроме аутентификации (которую я установил с помощью обработчика контекста для совместного использования токена аутентифицированного пользователя по всему приложению после того, как пользователь вошел в систему).
Все наши формы имеют от 2 до 10 полей, и после завершения данные просто передаются в наш бэкэнд через вызов api внутри ловушки. Образец формы показан ниже. Как видите, когда пользователь переходит к следующему полю, ничего не вводя в обязательное поле, граница ввода становится красной и появляется сообщение об ошибке. Все это происходит мгновенно, как вы позже увидите из мгновенной проверки после onBlur, onChange и onSubmit.
Если пользователь вводит что-то неверное, мы снова видим те же ошибки:
Самое замечательное в ловушке - это то, что она проверяет наличие ошибок ДО того, как даже задумываться о вызове api! Если пользователь отправляет запрос и ошибок НЕТ, то, наконец, запускается вызов API. Проверка на стороне клиента перед передачей данных формы на сервер - это еще один способ помочь отловить ошибки и передовой опыт.
ВАЛИДАЦИОННЫЙ КРЮЧОК
Итак, теперь, когда я рассказал о том, что делает крючок, и показал несколько примеров того, почему он хорош, вот крючок (далее мы рассмотрим его):
импортировать {useState, useEffect} из «реакции»;
// hook to validate all forms // accepts initial state for values, validation funtion, // and method to run when there are no errors const useFormValidation = (initialState, validate, runOnSubmit) => { const [values, setValues] = useState(initialState); const [errors, setErrors] = useState({}); // touched is an array of all fields that have // been touched by the user const [touched, setTouched] = useState([]); const [isSubmitting, setSubmitting] = useState(false); // use effect runs on intial component load // and every time the errors object changes useEffect(() => { // only call form submission if submit was hit if (isSubmitting) { const noErrors = Object.keys(errors).length === 0; // only call form submission if there are no errors if (noErrors) { // clear out touched upon submission setTouched([]); // run form submission when no errors runOnSubmit(); setSubmitting(false); } else { setSubmitting(false); } } // disabling the eslint error // reasons behind my opinions to not memoize functions // https://github.com/facebook/create-react-app/issues/6880 // isSubmitting also would trigger extra render if added // eslint-disable-next-line react-hooks/exhaustive-deps }, [errors]); // need to rerun after there is a changed to touched or values // this checks to see if there are any errors // that should be highlighted useEffect(() => { const validationErrors = validate(values); const touchedErrors = Object.keys(validationErrors) .filter(key => touched.includes(key)) .reduce((acc, key) => { if (!acc[key]) { acc[key] = validationErrors[key] } return acc }, {}) setErrors(touchedErrors); // eslint-disable-next-line react-hooks/exhaustive-deps }, [touched, values]); // handle change is run every time an input changes const handleChange = (event) => { setValues({ ...values, [event.target.name]: event.target.value }); } // handle blur is run when a form is tapped or // tabbed into/ 'touched' const handleBlur = (event) => { // if touched hasn't been touched before add it to the array if (!touched.includes(event.target.name)) { setTouched([ ...touched, event.target.name ]) } } // function for form submission const handleSubmit = (event) => { event.preventDefault(); const validationErrors = validate(values); setErrors(validationErrors); setSubmitting(true); } // return values from hook to be used in react form component return { handleSubmit, handleChange, handleChangeChecked, handleBlur, values, errors, isSubmitting }; } export default useFormValidation;
По сути, useEffect похож на componentDidMount и componentDidUpdate. Он запускается, когда компонент подключается к экрану, а затем запускается снова при изменении любой из зависимостей (переменных внутри массива).
У меня работают 2 эффекта использования, первый из которых используется для отправки - он проверяет, можно ли отправить форму / если есть ошибки, он сразу же отклоняет отправку. Второй проверяет форму. Он проверяет, касаются ли поля. Как только к нему прикасаются, он добавляется в массив. Кроме того, он проверяет форму на соответствие функции проверки, которая передается в ловушку. В конечном итоге, если произошла ошибка и было затронуто это поле формы, то эта конкретная ошибка добавляется к объекту ошибок.
ФАЙЛ ВАЛИДАЦИИ
Здесь вы устанавливаете то, что хотите проверить. Я привожу пару примеров, но вы можете проверить любое поле, используя такие вещи, как RegEx или javascript. Для следующих двух разделов я попытался выбрать несколько различных примеров, используемых в нашем веб-приложении. Этот файл принимает значения каждый раз, когда происходит изменение компонента или его отправка.
// validate function // pass in the values object const validateExample = (values) => { // initialize errors object let errors = {}; // if errors exist for each field then // add them to the errors object // First Name Errors // This is just saying if name doesn't exist if (!values.name) { errors.name = "Name is required"; } // Email Error // make sure email is correct format using RegEx // email is not required so only check if // email fits if one is provided if (values.email && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z] . {2,}$/i.test(values.email)) { errors.email = "Please enter a valid email address"; } const phoneno = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/ // Phone Number Errors if (!values.phoneNumber) { errors.phoneNumber = "Phone number is required"; } else if (!values.phone.match(phoneno)) { errors.phoneNumber = "Please enter a phone number with 10 digits. 1 not necessary" } // Date of Birth Errors // check to make sure all fields are provided // if they are make sure everything else is accurate if (!values.dateOfBirthYear) { errors.dateOfBirthYear = "Please enter your birth year"; } else if (values.dateOfBirthYear <= 1900 || values.dateOfBirthYear > 2019) { errors.dateOfBirthYear = "Please enter a valid year between 1900-2019"; } return errors; } export default validateExample;
КОМПОНЕНТ
Вот собственно реагирующий компонент. Здесь вы можете увидеть ловушку, вызываемую с помощью функции проверки сверху, и все выполняемые функции из ловушки. Обратите внимание, что значения onBlur, onChange и все используются с каждым вводом, а onSubmit используется после того, как форма будет заполнена и отправлена.
Над каждым вводом вы можете видеть, что отображается ошибка, если она существует, и использование ClassNames (потрясающий пакет npm). ClassNames позволяет выбирать классы на основе условной логики, поэтому при возникновении ошибки мы делаем контейнер ввода красным.
import React from 'react'; import styles from './componentExample.module.scss'; import useFormValidation from "./useFormValidation"; import validateExample from "./validateExample"; // great package for conditional classnames // need for dealing with error vs regular state // https://www.npmjs.com/package/classnames import classNames from 'classnames/bind'; const componentExample = () => { const INITIAL_STATE = { name: "", email: "", phoneNumber: "", dateOfBirthYear: "" } const submitFunction = async () => { const { name, email, phoneNumber, dateOfBirthYear } = values // here is where you would call something like an api // call your backend and post the form data } // call the hook const { handleSubmit, handleChange, handleBlur, values, errors, isSubmitting } = useFormValidation(INITIAL_STATE, validateExample, submitFunction); const { container, contentContainer, input, errorText, errorInput } = styles; return ( <div className={container}> <form onSubmit={handleSubmit}> <div className={contentContainer}> <div> Form is below </div> {/* if there is an error render error text */} {errors.name && <p className={errorText}>*{errors.name}</p>} {/* if there is an error make the input field use errorInput styling */} <input className={classNames(errors.name && errorInput, input)} name="name" placeholder="Name" type="text" onChange={handleChange} value={values.name} onBlur={handleBlur} /> {errors.email && <p className={errorText}>*{errors.email}</p>} <input className={classNames(errors.email && errorInput, input)} name="email" placeholder="Email" onChange={handleChange} value={values.email} onBlur={handleBlur} /> {errors.phoneNumber && <p className={errorText}>*{errors.phoneNumber}</p>} <input className={classNames(errors.phoneNumber && errorInput, input)} name="phoneNumber" placeholder="Phone Number" type="number" onChange={handleChange} value={values.phoneNumber} onBlur={handleBlur} /> {errors.dateOfBirthYear && <p className={errorText}>*{errors.dateOfBirthYear}</p>} <input className={classNames(errors.dateOfBirthYear && errorInput, input)} name="dateOfBirthYear" placeholder="YYYY" type="number" onChange={handleChange} value={values.dateOfBirthYear} onBlur={handleBlur} /> <button disabled={isSubmitting} type="submit"> Submit </button> </div> </form> </div> ) } export default componentExample;
CSS МОДУЛЬ
Наконец, вот CSS для ошибок. Я использую модули SASS и CSS, потому что они потрясающие.
.input { width: 300px; height: px; border-radius: 5px; border: solid 2px grey; margin-bottom: 16px; } .errorInput { border: solid 2px red; } .errorText { font-family: Roboto; font-size: 14px; color: red; width: 300px; }
Спасибо, что прочитали мою первую статью, и вот исходный код:
Надеюсь, это поможет всем избавиться от головной боли, связанной с формами, и узнать больше о том, как можно использовать хуки для упрощения повседневного кода :)