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

@qtpy/react-stm-form

v0.0.16

Published

Библиотека `createForm` предоставляет мощный и минималистичный инструмент для создания форм в React-приложениях с TypeScript. Она поддерживает конфигурацию полей через классы или объекты, позволяет задавать метки и сообщения об ошибках как примитивные дан

Downloads

32

Readme

Документация по использованию библиотеки createForm

Библиотека createForm предоставляет мощный и минималистичный инструмент для создания форм в React-приложениях с TypeScript. Она поддерживает конфигурацию полей через классы или объекты, позволяет задавать метки и сообщения об ошибках как примитивные данные, JSX-компоненты или динамические функции. Все примеры используют переиспользуемые компоненты Input и CheckBox для минимизации кода. В документации рассмотрены все методы библиотеки: onSubmit, handleSubmit, resetForm, watchField, useField, useFormStatus, setPortalData, getPortalData, addField, getField, watchField.

Содержание

  1. Основные возможности библиотеки

  2. Реализация компонентов
    2.1. Компонент Input
    2.2. Компонент CheckBox

  3. Примеры
    3.1 Использование классов
    3.2 Сброс формы с resetForm
    3.3 Наблюдение за полем с watchField
    3.4 Динамическое добавление полей с addField
    3.5 Получение данных поля с getField
    3.6 Динамические данные с setPortalData и getPortalData
    3.7 JSX для меток и сообщений
    3.8 Обработка onBlur

  4. Пример с асинхронной валидацией и debounced

  5. Заключение

Основные возможности библиотеки

  • Конфигурация полей: Поля задаются через классы или объекты. Поддерживаются строки, числа, булевы значения и JSX.
  • Валидация: Регулярные выражения или функции для проверки. Поля отслеживают состояния isTouched и isDirty.
  • Динамические данные: Метки и сообщения могут быть функциями, зависящими от значений или внешних данных.
  • Управление формой: Полный набор методов для отправки, сброса, наблюдения и добавления полей.
  • Переиспользуемые компоненты: Компоненты Input и CheckBox упрощают отображение текстовых полей и чекбоксов.

Компоненты

Компонент Input

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

import form from './myForm.ts';

interface InputProps {
  name: Exclude<keyof FormValueMap, 'consent'>;
}

const Input = React.memo(({ name }: InputProps) => {
  const field = form.useField(name);
  const hasError = field.isError;
  const hasValue = Boolean(field.value);
  const errorClass = hasError ? 'error' : '';
  const valueClass = hasValue ? 'noEmpty' : '';
  const inputId = `input-${name}`;

  return (
    <div className={`Input ${errorClass} ${valueClass}`}>
      <div className="Input_wrapper">
        <label htmlFor={inputId} className="Input-label">
          {field.label}
        </label>
        <input 
          id={inputId} 
          required={false} 
          className="Input-self" 
          onChange={field.onChange} 
          value={field.value} />
      </div>
      {hasError && field.message && (
        <p className="Input-message">{field.message}</p>
      )}
    </div>
  );
});
  • Принимает name, form и type (например, text или number).
  • Отображает метку, поле ввода и сообщение об ошибке.

Компонент CheckBox

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

import { PropsWithChildren } from 'react';
import form from './myForm.ts';

interface Props extends PropsWithChildren {
  name: 'consent';
}

const CheckBox = React.memo(({ name , children }) => {
  const field = form.useField(name);
  const formStatus = form.useFormStatus();
  const clIsActive = field.value ? 'active' : '';
  const clIsError = field.isError ? 'error' : '';

  const handleToggle = () => {
    field.onChange.setValue(!field.value);
  };

  return (
    <div className={`checkbox ${clIsActive} ${clIsError}`}>
      <div
        className="checkbox-icon"
        onClick={handleToggle}
        children={✔}
      />
      <p
        className="checkbox-text"
        children={children}
      />
      {formStatus.isSubmitted && field.isError && (
        <p className="checkbox-error">{field.message}</p>
      )}
    </div>
  );
});
  • Принимает name, form и children (текст для чекбокса).
  • Отображает чекбокс с иконкой и сообщением об ошибке после отправки формы.

Примеры использования методов

1. Форма с классами и чекбоксом (useField, useFormStatus, handleSubmit, onSubmit)

Пример формы с текстовым полем и чекбоксом:

import React from 'react';
import createForm from '@qtpy/react-stm-form';
import { FormFieldClass } from '@qtpy/react-stm-form/types';

class Username implements FormFieldClass {
  type: string;
  message = 'Некорректные символы';
  validate = /[a-zA-Zа-яА-Я]+$/;
  label(value: string) {
    return `Имя (${value.length} символов)`;
  }
}

class Consent implements FormFieldClass {
  type: boolean;
  message = 'Требуется согласие';
  label = 'Согласие';
  validate = /^true$/;
  initialValue = false;
}

type FormValueMap = {
  username: Username;
  consent: Consent;
};

const form = createForm<FormValueMap>({
  fields: {
    username: new Username(),
    consent: new Consent(),
  },
  delayOnBlur: 100,
});

const App = () => {
  const formStatus = form.useFormStatus();
  form.onSubmit((formData) => {
    console.log('Форма отправлена:', formData);
  });

  return (
    <div className="p-4">
      <Input name="username" />
      <CheckBox name="consent">
        Согласен с условиями
      </CheckBox>
      <button onClick={form.handleSubmit} disabled={!formStatus.isValid}>
        Отправить
      </button>
      {formStatus.isSubmitted && <p>Форма отправлена</p>}
    </div>
  );
};
  • useField: Получает данные поля.
  • useFormStatus: Возвращает isValid и isSubmitted.
  • handleSubmit: Запускает отправку формы.
  • onSubmit: Обрабатывает данные формы при отправке.

2. Сброс формы с resetForm

Сброс полей к начальным значениям:

import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  email: { type: string; label: string; message: string };
};

const form = createForm<FormValueMap>({
  fields: {
    email: {
      label: 'Электронная почта',
      message: 'Некорректный email',
      validate: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      initialValue: '[email protected]',
    },
  },
});

const App = () => {
  return (
    <div className="p-4">
      <Input name="email" />
      <button onClick={form.resetForm}>Сбросить</button>
    </div>
  );
};
  • resetForm: Сбрасывает значения полей, ошибки и isSubmitted.

3. Наблюдение за полем с watchField

Отслеживание изменений поля:

import React from 'react';
import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  username: { type: string; label: string; message: string };
};

const form = createForm<FormValueMap>({
  fields: {
    username: {
      label: 'Имя',
      message: 'Некорректное имя',
      validate: /[a-zA-Zа-яА-Я]+$/,
    },
  },
});

form.watchField('username',(field) => {
  console.log('Поле username:', field.value, 'Ошибка:', field.isError);
};

const App = () => {
  form.watchField('username', (field) => {
    console.log('Поле username:', field.value, 'Ошибка:', field.isError);
  }, 'react'); // можно отслеживать в react компоненте

  return (
    <div className="p-4">
      <Input name="username" />
    </div>
  );
};
  • watchField: Вызывает callback при изменении состояния поля.

4. Динамическое добавление полей с addField

Добавление нового поля:

import createForm from '@qtpy/react-stm-form';
import { KeyPattern } from '@qtpy/react-stm-form/types';

type FormValueMap = {
  task: { type: string; label: string; message: string };
  [key: `task_${number}`]: { type: string; label: string; message: string };
  // или
  [key: KeyPattern<'child'>]: { type: boolean; label: string; message: string };
  // чтобы проще исключать поля: Exclude<keyof FormValueMap, KeyPattern<'child'>>;
};

const form = createForm<FormValueMap>({
  fields: {
    task: {
      label: 'Задача',
      message: 'Поле не может быть пустым',
      validate: /.*$/,
    },
  },
});

const App = () => {
  form.addField('task_1', {
    label: 'Доп. задача',
    message: 'Поле не может быть пустым',
    validate: /.*$/,
  });
  console.log(form.getField('task_1'));

  return (
    <div className="p-4">
      <Input name="task" />
      <Input name="task_1" />
    </div>
  );
};
  • addField: Добавляет поле с указанной конфигурацией.

5. Получение данных поля с getField

Получение текущего состояния поля:

import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  username: { type: string; label: string; message: string };
};

const form = createForm<FormValueMap>({
  fields: {
    username: {
      label: 'Имя',
      message: 'Некорректное имя',
      validate: /[a-zA-Zа-яА-Я]+$/,
    },
  },
});

const App = () => {
  const field = form.getField('username');
  console.log('Состояние username:', field.value, field.isError);

  return (
    <div className="p-4">
      <Input name="username" />
    </div>
  );
};
  • getField: Возвращает данные поля (значение, ошибка, метка).

6. Динамические данные с setPortalData и getPortalData

Передача и получение данных для сообщений и валидации:

import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  phone: {
    type: string;
    label: string;
    message: (value: string, data: number) => string;
  };
};

const form = createForm<FormValueMap>({
  fields: {
    phone: {
      label: (value, data) => `У тебя хороший номер, прям ${data}: ${value} `,
      message: (value, data) => `Нужно ${data} цифр`,
      validate: (value, data) => value.length === Number(data),
    },
  },
});

const App = () => {
  form.setPortalData('phone', 'message', 10);
  form.setPortalData('phone', 'validate', '10');
  form.setPortalData('phone', 'label', '10/10');
  const messageData = form.getPortalData('phone', 'message');
  console.log('Данные для сообщения:', messageData);

  return (
    <div className="p-4">
      <Input name="phone" />
    </div>
  );
};
  • setPortalData: Задаёт данные для сообщения или валидации.
  • getPortalData: Получает установленные данные.

7. JSX для меток и сообщений

Кастомизация с помощью JSX:

import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  username: { type: string; label: React.JSX.Element; message: React.JSX.Element };
};

const form = createForm<FormValueMap>({
  fields: {
    username: {
      label: <b style={{ color: 'blue' }}>Имя</b>,
      message: <span style={{ color: 'red' }}>Ошибка имени</span>,
      validate: /[a-zA-Zа-яА-Я]+$/,
    },
  },
});

const App = () => {
  return (
    <div className="p-4">
      <Input name="username" />
      <button onClick={form.handleSubmit}>Отправить</button>
    </div>
  );
};
  • Метка и сообщение — JSX-компоненты.

Обработка onBlur и onFocus

import React from 'react';
import createForm from '@qtpy/react-stm-form';

type FormValueMap = {
  username: { type: string; label: string; message: string };
};

const form = createForm<FormValueMap>({
  fields: {
    username: {
      label: 'Имя',
      message: 'Некорректное имя',
      validate: /^[a-zA-Zа-яА-Я]+$/,
    },
  },
  delayOnBlur: 1000, // время когда поменяется обратно на false
});

form.watchField('username', (field) => {
  console.log('Поле username:', {
    value: field.value,
    isTouched: field.isTouched,
    isError: field.isError,
  });
}

const Input = ({ name }: { name: keyof FormValueMap }) => {
  const field = form.useField(name);

  return (
    <div>
      <label>
        {field.label} {field.isTouched && <span>(активно)</span>}
      </label>
      <input 
        onChange={field.onChange}
        onBlur={field.onBlur}
        value={field.value}
      />
      {field.isError && <p>{field.message}</p>}
    </div>
  );
};

const App = () => (
  <div className="p-4">
    <Input name="username" />
  </div>
);

Пример с асинхронной валидацией и debounced

Простой пример формы с полем для номера телефона, где валидация выполняется асинхронно с debounced-задержкой.

import createForm from '@qtpy/react-stm-form';
import { FormFieldClass, CreateFormReturn, DebouncedFn } from '@qtpy/react-stm-form/types';


class Phone implements FormFieldClass {
  type: string = 'string';
  label: string = 'Телефон';
  message: string = 'Проверяем...';
  prevValue: string = '';
  debounced!: DebouncedFn<[string, boolean]>;
  form?: CreateFormReturn<FormValueMap>;

  init = (form: CreateFormReturn<FormValueMap>) => {
    this.form = form;
    this.debounced = form.debounced(async (value: string, isValid:boolean) => {
      // Имитация асинхронного запроса (3 секунды)
      await new Promise((resolve) => setTimeout(resolve, 3000));
      this.prevValue = value;
      this.message = isValid ? 'Валидно!' : 'Только цифры!';
      console.log(`Проверено: ${value}`);
    }, 1000); // Задержка 1 секунда
  };

  validate(value: string) {
    const isValid = /^\d+$/.test(this.prevValue);
    this.debounced(value, isValid);
    return isValid;
  }
}

type FormValueMap = {
  phone: Phone;
};


const form = createForm<FormValueMap>({
  fields: { phone: new Phone() },
});

form.getField('phone').init(form) // передаем нашу форму

const App = () => (
  <div>
    <h1>Асинхронная форма</h1>
    <Input name="phone" />
    <button onClick={form.handleSubmit}>Отправить</button>
  </div>
);

export default App;

Суть:

  • При потере фокуса (onBlur) поле username помечается как isTouched: true, отображается "(активно)".
  • Через 1 секунду (delayOnBlur) isTouched сбрасывается в false (асинхронно через asyncUpdate.quiet), надпись исчезает.
  • abortPrevious: true отменяет предыдущий таймер при повторном onBlur.

Заключение

Библиотека createForm упрощает создание форм в React с TypeScript. Компоненты Input и CheckBox минимизируют код, а методы onSubmit, handleSubmit, resetForm, watchField, useField, useFormStatus, setPortalData, getPortalData, addField, getField обеспечивают гибкое управление формами.