# formsy-react: building your own inputs and getting validation for free

> formsy-react gives React developers a form container and a withFormsy higher order component, so a custom input joins the form's validation, submission and disabled state just by wrapping it. The API is stable, and it still ships as a class based higher order component in a world of hooks.

**formsy/formsy-react** — A form input builder and validator for React JS

- Repository: https://github.com/formsy/formsy-react
- Stars: 762 · Forks: 124
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/formsy-formsy-react

## The problem the library was written to solve

formsy-react is a form input builder and validator for React, and it exists because of a specific article. Christian Alfoni wrote a piece on forms and validation with React called Nailing that validation with React JS, and the library grew out of it. The project was originally located at christianalfoni/formsy-react, with the note that old issues live there, and the current repository is under the formsy organisation.

The framing of the problem is worth quoting because it explains the design. The main concept is that forms, inputs and validation are done very differently across developers and projects, and the component aims to be that sweet spot between flexibility and reusability.

What the library actually gives you is a container and a wrapper. You write the input you want, a styled text field, a date picker, a masked phone number, and you wrap it in withFormsy. The wrapper connects it to the form, and from that point the field is validated, participates in submission, and can be told to show an error. The list of capabilities in the README says it plainly: build any kind of form element component, not just traditional inputs, and get validation for free; add validation rules and use them with simple syntax; use handlers for the different states of your form; pass external errors to the form to invalidate elements, for example a response from a server; and dynamically add form elements that register and unregister themselves.

That last one matters for forms whose shape depends on earlier answers, and the fourth matters for the common case where the server is the real source of truth.

## A custom input becomes a form element by being wrapped

The quick start is two steps. The first is to build a Formsy element, which is an ordinary React component that calls setValue when its value changes:

```jsx
import { withFormsy } from 'formsy-react';
import React from 'react';

class MyInput extends React.Component {
  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this);
  }

  changeValue(event) {
    // setValue() will set the value of the component, which in
    // turn will validate it and the rest of the form
    // Important: Don't skip this step. This pattern is required
    // for Formsy to work.
    this.props.setValue(event.currentTarget.value);
  }

  render() {
    // An error message is passed only if the component is invalid
    const errorMessage = this.props.errorMessage;

    return (
      <div>
        <input onChange={this.changeValue} type="text" value={this.props.value || ''} />
        <span>{errorMessage}</span>
      </div>
    );
  }
}

export default withFormsy(MyInput);
```

Three details in that code are load bearing. The component must call setValue on change, and the comment calls it out as required for Formsy to work, because the form has no other way to learn the value. The component reads errorMessage from props, and the comment explains that an error message is passed only if the component is invalid, so the component can render the error unconditionally and get nothing when there is no error. And the value is read back from this.props.value rather than from local state, which is what makes the form the single source of truth.

withFormsy is a Higher-Order Component, and the API documentation has the complete list of props it exposes. The second step is to use that element inside a Formsy container, which is where the rules and the state handlers live.

## The Formsy container holds the rules and the form state

The second half of the quick start is the container:

```jsx
import Formsy from 'formsy-react';
import React from 'react';
import MyInput from './MyInput';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.disableButton = this.disableButton.bind(this);
    this.enableButton = this.enableButton.bind(this);
    this.state = { canSubmit: false };
  }

  disableButton() {
    this.setState({ canSubmit: false });
  }

  enableButton() {
    this.setState({ canSubmit: true });
  }

  submit(model) {
    fetch('http://example.com/', {
      method: 'post',
      body: JSON.stringify(model),
    });
  }

  render() {
    return (
      <Formsy onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
        <MyInput name="email" validations="isEmail" validationError="This is not a valid email" required />
        <button type="submit" disabled={!this.state.canSubmit}>
          Submit
        </button>
      </Formsy>
    );
  }
}
```

The result of that code is a form with a submit button that runs the submit method when the form is submitted with a valid email. The button stays disabled while the input is empty and while the value is not an email, and the validation error message shown is the one passed in the validationError prop.

The API is prop driven throughout. The rules live on the field as validations="isEmail", the human message as validationError, and the required flag as a separate boolean. The form state is exposed through three handlers, onValidSubmit for a valid submission with the model, onValid and onInvalid for the transitions, which is the mechanism the example uses to enable and disable the button.

The model passed to onValidSubmit is what you send to the server, and in the example it is JSON stringified into a POST.

## The 1.x to 2.x rename, and why the API looks the way it does

The 2.0 release fixed a number of legacy decisions in the Formsy API, mostly a reliance on function props over value props passed down to wrapped components. The API changes are described as minor, and they are listed as a straight rename list, with the getter style on the left and the prop style on the right: getErrorMessage() becomes errorMessage, getErrorMessages() becomes errorMessages, getValue() becomes value, hasValue() stays hasValue, isFormDisabled() becomes isFormDisabled, isFormSubmitted() becomes isFormSubmitted, isPristine() becomes isPristine, isRequired() becomes isRequired, isValid() becomes isValid, showError() becomes showError and showRequired() becomes showRequired.

Two entries are unchanged, which is the kind of detail that tells you the list was written by copying the old API rather than redesigning it.

The rationale is the useful part. A function prop forces the consumer to call something to learn a value, while a value prop is passed down and read. The move from the first to the second is a change in who controls the data, and it is the same direction React itself moved in with hooks and the move away from class lifecycle methods.

There is a companion set of component packages listed for people who want a ready-made look rather than their own. twisty/formsy-react-components provides Bootstrap components for a Formsy-React form, and zabute/formsy-semantic-ui-react provides Semantic-UI-React components for one. That is a reasonable division, since the point of the library is that you can bring your own components instead.

## The build and toolchain the package ships with

The package manifest describes a TypeScript source that is built with rslib, the build tool from the Rsbuild ecosystem. The build script is rslib, with a watch variant, and there is a separate rslib.config.ts file at the top of the tree.

The published output is a single dist directory, and the files field lists only dist. The package is marked as an ES module, with main pointing at dist/index.cjs, module at dist/index.js, and types and typings both at dist/index.d.ts. The exports map gives the modern entry with a types condition, an import condition pointing at the ES module build, and a require condition pointing at the CommonJS build, so the package resolves correctly for both bundlers and older tooling.

The runtime dependency list is short, with lodash as the only production dependency. Everything else is a development dependency, and the list shows the shape of the tooling: the rslib core and the React plugins for the build, SWC with a jest transform for the tests, jest itself with a jsdom environment, testing-library packages for React and DOM, oxlint for linting, oxfmt for formatting, and the usual release tooling in auto-changelog and bumpp. The scripts reflect that: lint runs oxlint, format runs oxfmt --write, test runs jest --passWithNoTests, and release runs bumpp --all.

Two details are worth flagging. The test script passes with no tests, so a green test run is not by itself evidence that the suite ran, and the repository does carry __tests__ and __test_utils__ directories, so the tests exist. The coverage badge points at Coveralls and the CI badge at a GitHub Actions workflow.

On the release side, version 2.4.6 is the current version in the manifest and the latest tag listed, published on 2026-07-29, with 2.4.5 on 2026-05-19 and 2.4.4 on 2026-05-18. The last push was on 2026-10-17, so the project is still being worked on.

## Where formsy-react is the wrong tool

The first wrong case is a codebase that has moved to hooks. The documented pattern is a class component with a bound change handler, and the whole set of props the wrapper exposes was designed around that shape. A function component with hooks can be made to work, but the value has to be pushed up with setValue on every change, which is the ceremony the library cannot remove. A schema-first validator, where you describe the shape of the data once and generate both the rules and the form, is a different model that this library does not implement.

The second wrong case is a form that is mostly plain HTML inputs with a couple of unusual fields. The library's value comes from wrapping components that are not standard inputs. If you have a form of native inputs, a small amount of validation code in the component tree is likely less machinery than a container, a higher order component and a model object.

The third is the state model itself. The form is the source of truth for the values, and the fields read them back from props rather than holding local state. That is coherent, but it means two-way binding between the form and your own state has to be arranged deliberately, and there is no documented recipe for it in the quick start.

The fourth is the age of the API surface. The library predates the current React idioms, the project is MIT licensed and unarchived, and the 1.x to 2.x migration shows a project that has changed its API shape once already. It is stable in the sense that it is widely used and long lived, not in the sense that it is tracking current React practice.

## Conclusion

Adopt formsy-react if you build forms out of components that are not plain HTML inputs, because the whole point of the library is that a custom control gets validation, error display and form state for free once it is wrapped in withFormsy. Keep away if you want a schema-first validator or if your codebase has moved to hooks and function components, since the documented pattern is still a class component calling setValue on every change. Before you upgrade from the 1.x line, read the upgrade guide in the README, because version 2 renamed a dozen get-prefixed methods into plain props, and check the 2.4.6 release from 2026-07-29 against your React version.

## FAQ

### What does formsy-react do for React forms?

It provides a form container and a withFormsy higher order component. Any component you wrap with withFormsy joins the form's validation, error display and submission, so a custom input gets validation for free rather than being tied to native input elements.

### How do I add a validation rule to a field in formsy-react?

Rules are props on the field, such as validations="isEmail", with the human readable message given by validationError and the required flag set separately with required. The form then disables the submit button until the fields are valid, and onValid and onInvalid handlers report the transitions.

### What changed between formsy-react 1.x and 2.x?

The 2.0 release replaced function props with value props, and the README lists the renames: getErrorMessage() to errorMessage, getValue() to value, isValid() to isValid and similar changes across about a dozen methods, with hasValue() unchanged.

### How do I install formsy-react?

Run yarn add formsy-react react react-dom, then use it with a bundler such as webpack or browserify. The package ships TypeScript types, with the declaration file at dist/index.d.ts, and the manifest is set to type module with both import and require export conditions.

## Sources

- [formsy/formsy-react on GitHub](https://github.com/formsy/formsy-react)
- [Issues](https://github.com/formsy/formsy-react/issues)
- [License: MIT](https://github.com/formsy/formsy-react/blob/master/LICENSE)
- [README](https://github.com/formsy/formsy-react/blob/master/README.md)
- [Releases](https://github.com/formsy/formsy-react/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/formsy-formsy-react
