npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

modern-react-form

v0.1.1

Published

Modern react form based on hooks and division of responsibility

Readme

ModernForm. Конструктор форм для Реакта.

ModernForm преследует несколько целей:

  1. Переместить детали реализации из компонента в отдельный модуль чтобы не смешивать вид и логику;
  2. Дать возможность настраивать поведение формы просто указывая параметры поведения.

Вы будите взаимодействовать с двумя сущностями: объектом конфигурации формы и хуком useGetModernForm(). В объекте вам нужно сконфигурировать форму. А через хук получить данные для взаимодействия с формой.

Смотрите примеры использования в папке examples.

Объект конфигурации формы

В объекте конфигурации указываются настройки формы. Он должен соответствовать типу MFTypes.Config. Поэтому там должны быть два основных свойства: fields, requestFn и необязательное settings.

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

В requestFn асинхронная функция запускаемая при отправке формы. Самый распространённый вариант — это отправка данных на сервер.

В settings объект с настройками поведения формы.

const formConfig: MFTypes.Config = {
    fields: {...},
    async requestFn(/*...*/) {/*...*/},
    settings: {/*...*/},
}

Ниже рассказаны детали.

Свойство fields в объекте конфигурации формы

Тут указывается объект с конфигурацией полей существующих в форме.

В ключе пишется строка, которая будет являться именем поля. Другими словами значение атрибута name. По этому ключу далее вы будите получать объект со свойствами, которые нужно передать в компонент поля формы. В этом объекте будет имя поля, значение, обработчики изменения и потери фокуса и прочие данные (об этом рассказано подробнее в разделе «Хук useGetModernForm»).

Рассмотрим свойства в объектах настройки полей.

В свойстве fieldType указывается тип поля. Всего существует 5 значений:

  1. text — текстовое поле или текстовая область;
  2. radio — группа переключателей (radio);
  3. checkbox — группа флагов (checkbox);
  4. toggle — тумблер. Фактически это одиночный флаг. Поэтому такое поле может быть или в состоянии «включено» или «выключено»;
  5. select — выпадающий список

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

Поля типа radio и checkbox должны иметь свойство inputs, где указан массив переключателей и флагов. Они описываются через объекты со свойствами label (подпись поля) и value (значение поля). Аналогичный массив нужно передать полям типа select. Только там он заносится в свойство options.

В поле типа toggle передайте свойство value со значением флага.

В примере ниже дан пример формы со всеми типами полей и всеми свойствами. Только schema есть у одного поля, но можно поставить на любые другие.

const formConfig: MFTypes.Config = {
	fields: {
		name: {
			fieldType: 'text',
			schema(fields, value) {
				if (value.length < 3) return 'Имя должно быть длиннее двух символов'
				else if (value.length > 20) return 'Имя не должно быть длиннее 20 символов'
				return null
			}
		},
		gender: {
			fieldType: 'radio',
			inputs: [
				{ value: 'without-answer', label: 'Без ответа' },
				{ value: 'man', label: 'Мужчина' },
				{ value: 'woman', label: 'Женщина' },
			],
		},
		country: {
			fieldType: 'select',
			options: [
				{ value: 'rus', label: 'Россия' },
				{ value: 'ukr', label: 'Украина' },
				{ value: 'bel', label: 'Беларусь' },
			],
		},
		legal: {
			fieldType: 'checkbox',
			inputs: [
				{ label: 'Принятие правил сервиса', value: 'acceptRules' },
				{ label: 'Ценовая политика', value: 'pricesPolicy' },
			],
		},
		getNews: {
			fieldType: 'toggle',
			value: 'toggle',
		},
	},
	async requestFn(/*...*/) {/*...*/},
	settings: {/*...*/},
}

Функция проверяющая значение поля

В примере выше показан пример такой функции:

const formConfig: MFTypes.Config = {
	fields: {
		name: {
			fieldType: 'text',
			schema(fields, value) {
				if (value.length < 3) return 'Имя должно быть длиннее двух символов'
				else if (value.length > 20) return 'Имя не должно быть длиннее 20 символов'
				return null
			}
		}
	},
	async requestFn(/*...*/) {/*...*/},
	settings: {/*...*/},
}

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

Ниже показан пример как можно использовать Yup для более простой и качественной проверки значения полей формы.

const allFieldsFormConfig: MFTypes.Config = {
	fields: {
		text: {
			fieldType: 'text',
			schema(fields, value) {
				return checkTextField(() => {
					yup.string()
						.required('Обязательное поле')
						.email('Нужно написать почту')
						.max(8, 'Максимум можно использовать 8 символов')
						.validateSync(value)
				})
			}
		},
	},
	async requestFn(/*...*/) {/*...*/},
	settings: {/*...*/},
}

function checkTextField(fn: Function): string | null {
	try {
		return fn()
	}
	catch (err) {
		if (err instanceof Error) {
			return err.message
		}
	}
	
	return null
}

Yup больше подходит для проверки строк на соответствие каким-либо критериям. То есть им лучше проверять значения текстовых полей. В случае флагов, переключателей, выпадающих списков и тумблера это всё не требуется потому что там передаётся value выделенного значения (в случае флагов массив выделенных значений). И всё, что вы сможете проверить это проставлено ли значение или нет.

Свойство requestFn в объекте конфигурации формы

Тут указывается функция запускаемая при отправке формы. requestFn получает объект со значениями всех полей

Метод должен вернуть объект { status: 'success' } если запрос прошёл удачно или объект { status: 'fail' } если не удачно. Дополнительно туда можно поставить свойство commonError с текстом ошибки не привязанной к конкретному полю. Например «На сервере произошла неизвестная ошибка» или «Неправильная почта или пароль». Если ошибки привязаны к полям, то их нужно указать в свойстве fieldsErrors. Он принимает массив объектов типа { name: string, message: string }. В name указывается название поля, а в message текст ошибки.

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

Ниже показан пример функции эмулирующий запрос к серверу и через секунду получающий успешный ответ.

const formConfig: MFTypes.Config = {
    fields: {/*...*/},
    
    async requestFn(readyFieldValues) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({ status: 'success' })
            }, 1000)
        })
    },
    
    settings: {/*...*/},
}

Свойство settings в объекте конфигурации формы

Это необязательный параметр в который вы можете передать объект регулирующий поведение формы.

В checkBeforeSubmit указывается при каком событии форма должна проверять правильность полей до отправки формы: never (не проверять), onChange (при изменении значения поля), onBlur (при потерей поля фокуса).

В checkAfterSubmit указывается при каком событии форма должна проверять правильность полей после отправки формы: onChange (при изменении значения поля), onBlur (при потерей поля фокуса). Тут имеется в виду если пользователь попытался отправить форму, она проверилась, были найдены ошибки, и этот параметр регулирует при каком событии нужно эти ошибки проверять.

В disableFields указывается настройка блокировки полей: never (не блокировать), whileSubmit (при отправке формы и разблокировать после получения ответа), whileAndAfterSubmit (при отправке формы и после получения ответа от сервера), afterSubmit (после получения ответа от сервера). Если в полях есть ошибки, то поля блокироваться не будут.

В clearFieldsAfterSubmit указывается булево значение нужно ли очищать поля после удачной отправки формы.

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

Объект ниже показывает значения по умолчанию:

const formConfig: MFTypes.Config = {
    fields: {/*...*/},
    async requestFn(/*...*/) {/*...*/},
    
    settings: {
		checkBeforeSubmit: 'never',
		checkAfterSubmit: 'onChange',
		disableFields: 'never',
		clearFieldsAfterSubmit: false,
		sendFormOnFieldBlur: false,
		sendFormOnFieldChange: false,
    },
}

Хук useGetModernForm()

В функции компонента, где будет форма, нужно вызвать хук useGetModernForm() в который передать объект конфигурации формы. Он возвратит объект со следующими свойствами:

В fieldAttrs объект с атрибутами, которые нужно передать в поля формы. В следующем разделе дан список передаваемых атрибутов.

В onSubmit функция-обработчик отправки формы. Поставьте в свойство onSubmit элемента <form>.

В formHasErrors булево значение имеет ли форма ошибки в настоящий момент.

В commonError текст общей ошибки формы или null.

В submitStatus статус отправки формы: waiting — форма ещё не отправлялась, pending — форма отправилась, но ответ ещё не получен, error — после отправки формы пришёл ошибочный ответ и success после отправки формы пришёл успешный ответ.

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

function AllFieldsForm() {
	const {
		fieldAttrs,
		onSubmit,
		formHasErrors,
		commonError,
		submitStatus
	} = useGetModernForm(allFieldsFormConfig)
	
	return (
		<>
            <form onSubmit={onSubmit}>
	            <TextInput {...fieldAttrs.name} label='Name' />
	            <FieldGroup {...fieldAttrs.gender} label='Gender' inputType='radio' />
	            <country {...fieldAttrs.select} label='Country' />
	            <FieldGroup {...fieldAttrs.legal} inputType='checkbox' />
	            <Toggle {...fieldAttrs.getNews} />
	            <Button type='submit' text='Send' disabled={formHasErrors || submitStatus === 'pending'}/>
	        </form>
	        {commonError && <p>{commonError}</p>}
	    </>
	)
}

Компоненты-поля формы

Как было сказано хук useGetModernForm() возвращает объект со свойством fieldAttrs. В его ключах имена полей. А в значении каждого ключа объект с атрибутами, которые нужно передать в компонент через механизм деструктуризации ({...props}). Эти свойства отличаются в зависимости от типа поля.

text

  • mName: string
  • mValue: string
  • mError: null | string
  • mDisabled: boolean
  • mOnChange: Function
  • mOnBlur: Function

radio и checkbox

  • mName: string
  • mInputs: [{label: '', value: '', checked: false}]
  • mError: null | string
  • mDisabled: boolean
  • mOnChange: Function
  • mOnBlur: Function

select

  • mName: string
  • mValue: string
  • mError: null | string
  • mDisabled: boolean
  • mOptions: [{label: '', value: ''}]
  • mOnChange: Function
  • mOnBlur: Function

toggle

  • mName: string
  • mValue: string
  • mChecked: boolean
  • mError: null | string
  • mDisabled: boolean
  • mOnChange: Function
  • mOnBlur: Function

В mName имя поля. То, что должно быть в атрибуте name.

В mError текст ошибки.

В mDisabled булево значение заблокировано ли поле.

В mOnChange и mOnBlur обработчик изменения значения поля и потерей фокуса.

В поле типа text передаётся атрибут mValue со значением поля.

В поле типа radio и checkbox передаётся атрибут mInputs с массивом переключателей или флагов.

В поле типа select передаётся атрибут mOptions с массивом пунктов выпадающего списка. А выбранное значение в атрибуте mValue.

В поле типа toggle через mValue передаётся значение поля и через mChecked выбрано ли поле.

Все эти данные вам нужно поставить в свои компоненты полей формы. Это делается 1 раз. И ModernForm будет обновлять их значение.

Заметьте, что компоненты для типов полей radio и checkbox должны уметь отрисовывать не единичные переключатели или флаги, а их группу потому что в них передаётся массив данных. Проще всего сделать один такой компонент принимающий тип полей, массив данных и отрисовывающий поля. Пример такого компонента смотрите на formElements/FieldGroup/FieldGroup.

Чтобы лучше понять как это сделать советую посмотреть примеры в папке examples.

Скорее всего у вас затипизирован объект props передаваемый в компонент. Там могут быть как ваши свойства (например label, inputType и др.), так и свойства, которые вы обязаны передать для правильной работы полей формы (они начинаются на m...).

export type TextInputPropType = {
	label?: string 
	inputType?: 'text' | 'textarea'
	fieldType?: 'text' | 'email' | 'password'
	rows?: number
	mName: string
	mValue: string
	mError?: null | string
	mDisabled?: boolean
	mOnChange: Function
	mOnBlur?: Function
}

function TextInput(props: TextInputPropType) {/*...*/}

Чтобы не типизировать эти передаваемые свойства можно наследоваться от типа MFTypes.TextCompProps. Это самый правильный вариант.

export type TextInputPropType = MFTypes.TextCompProps & {
	label?: string
	inputType?: 'text' | 'textarea'
	fieldType?: 'text' | 'email' | 'password'
	rows?: number
}

function TextInput(props: TextInputPropType) {/*...*/}