Привет! Это моя первая статья на Хабре и в ней я хочу рассказать о, первой мной написанной, библиотеке для работы с формами. Но сперва, давайте поговорим о том, как должна выглядеть идеальная библиотека для работы с формами.

  1. Синтаксис будто создан для написания форм.

  2. Формы просто дебажить и, еще проще, облагать их тестами.

  3. Полная инкапсуляция логики конкретных инпутов.

  4. Компоненты инпутов должны быть реюзабельными.

  5. Конечная форма должна быть максимально абстрактной и легко читаемой.

А теперь, давайте посмотрим, как выглядит форма написанная с помощью react-redux-hook-form.

export const Login:FC = memo( () => {
  const form = useForm({name: 'login'})

  return (
    <WrapperForm form={form} onSubmit={(data:ILogin) => {User.options.login(data)}}>
      <InputTitleStyled>Номер телефона</InputTitleStyled>
      <PhoneInput name={'username'} required/>

      <InputTitleStyled>Пароль</InputTitleStyled>
      <PasswordInput name={'password'} required/>

      <SubmitButton text={'Авторизироваться'}/>
    </WrapperForm>
  )
})

Здесь мы используем хук useForm и обертку для формы WrapperForm.

useForm - хук для инициализации формы, принимает name и возвращает объект с методами для работы с формой. form.reset, form.changeDataField, form.changeIsValidateField, form.changeMessageErrorField, form.useFieldSelector. Полагаю, не имеет смысла описывать принцип работы каждого метода.

WrapperForm - обертка формы в которую обязательно должна быть обернута форма. Принимает параметры: onChange, initialValue, onSubmit.

Теперь давайте посмотрим, как создаются инпуты.

interface IEmail {
  name: string;
  required?: boolean;
}

export const EmailInput: FC<IEmail> = (props) => {
  const {useData, useIsValidate, useIsTouch, useMessageError} = useField({
    name: props.name,
    isRequired: props.required,
    validateFunction: (value:string) => validate(value).string().max(30).email(),
  })

  const [data, changeData] = useData()
  const [isValidate, ] = useIsValidate()
  const [isTouch, ] = useIsTouch()
  const [messageError, ] = useMessageError()

  return (
    <>
      <EmailInputStyled
        type={"email"} 
				value={data}
        onChange={(e:any)=> {changeData(e.target.value)}}
        isValidate={isValidate || !isTouch}
        id={props.name}
      />
      {!isValidate && isTouch &&
        <ErrorTitleStyled className={'error'}>
          {messageError}
        </ErrorTitleStyled>
      }
    </>
  )
};

Здесь мы используем useField. useField - инициализирует поле и из него достаются хуки для работы с полем. useData, useIsValidate, useIsTouch, useMessageError, работают по принципу useState. Validate/IsTouch/MessageError - автоматически обрабатываются, но если вы хотите самостоятельно обрабатывать данные поля, библиотека позволит вам это сделать. Для того, чтобы отменить автоматическую обработку, необходимо в useField передать isDisableAuto: true.

Параметры useField: name, initialValue, isRequired, isDisableAuto, messageError, isTouch, isValidate, validateFunction.

name: название поля. Все поля должны быть уникальными.

initialValue: стандартное значение. Стоит использовать для того, чтобы явно указать, в каком формате будут храниться данные в данном инпуте. Перебивается initialValues с формы.

Теперь давайте посмотрим, как создать кнопку, для данной формы.

interface ISubmitButton {
  text: string;
  className?: string;
}

export const SubmitButton: FC<ISubmitButton> = (props) => {
  const formName = useContext(formNameContext)
  const isValidForm = useIsValidForm(formName)
  const onSubmit = useContext(onSubmitContext)

  const onClick = () => {
    if(isValidForm){
      onSubmitForm(formName, onSubmit)
    }
  }
  return (
    <SubmitButtonStyled className={props.className} isValidForm={isValidForm} onClick={onClick}>
      {props.text}
    </SubmitButtonStyled>
  )
};

В данном примере мы достаем из контекста formName и onSubmit.

Также мы используем useIsValidateForm хук, в которым передаем имя формы.

Так-как мы используем redux. То мы можем использовать данные формы в других местах.

Например:

const search = useFieldSelector({formName: 'searchForm', fieldName: 'search'})

Подключение react-redux-hook-form.

Сперва установим: npm i react-redux-hook-form.

Затем надо модифицировать стор.

1) Подключим formControllerReducer.

import { formControllerReducer } from 'react-redux-hook-form';

const combinedReducer = combineReducers({
  formController: formControllerReducer, // подключение formControllerReducer
  user: userReducer,
  contact: contactReducer,
});

2) Запишем функцию getState в window.

window.getState = () => store.getState()

В общем-то это все что нужно знать для того чтобы использовать react-redux-hook-form.

Так как мы используем redux, мы можем дебажить форму через redux-devtools(расширения гугл хрома).

Думаю, еще стоит уделить пару строчек себе. У меня не так много коммерческого опыта, всего лишь 1.5 года. да и то треть из этого опыта это работа на бэке. Так что жду предложению по тому, как можно улучшить данную библиотеку :)

Комментарии (4)


  1. Alexandroppolus
    02.06.2022 20:59
    +2

    Запишем функцию getState в window.

    это зачем?? Редуксовый стор лежит в контексте и должен извлекаться через хук useStore.

    Ссылку на гитхаб не нашёл.


  1. Hystrider
    02.06.2022 21:27
    +2

    А чем плох уже популярный пакет React Hook Form?)

    Постоянно работаю со сложными формами в реакте, (правда без Redux) и самые сложности возникают с встраиванием в такие пакеты готовых виджетов (по типу react-select, flatpicker, uppy), а также динамическими зависимыми частями форм (появляющимися, исчезающими), динамически меняющимися начальными значениями и. т. д.)

    В статье в принципе показано как работать с простыми инпутами, но не сказано о преимуществах и недостатках относительно других решений ????

    А в целом, интересно почитать как другие люди в реакте с формами борятся)


  1. kubk
    02.06.2022 22:32
    +2

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

    Не вы первый, не вы последний сталкиваетесь с такой проблемой. По моему опыту, это лечится со временем :) По теме - хранить формы в глобальном сторе не рекомендует даже создатель Redux. Это была одна из причин почему, например, проект redux-form в своё время закрылся.


  1. Aketca
    04.06.2022 13:39

    Компоненты оболочки для инпутов пишутся за пару часов, под конкретные технологии проекта, учитывая дизайнерские фичи. И потом используются во всём проекте. Основные сложности бывают в имплементации всяких селектов, календарей итд, но это сложности на один раз. Что проще, сделать кастомный инпут с нуля учитывая особенности проекта или кастомизировать вашу либу?

    Не в обиду автору, но мне кажется, что данное решение не является серебряной пулей, уже есть множество вариантов решений для работы с формами и ни одно никогда не покрывало 100% запросов из коробки.