1.0.15 • Published 3 years ago

formy v1.0.15

Weekly downloads
86
License
-
Repository
github
Last release
3 years ago

Formy

Formy is a form generation library in React. Create your form as a JS object and render it however you want.

Comes with helper functions for input events to maintain internal state.

Benefits

  Total separation of data and layout

We were tired of input attributes getting mixed in with the HTML markup of a form.

Declare an input's state as a simple JS object and free up your HTML for what it's best for: layout.

A text input is now <Form.Field/>. A dropdown with a million options is now <Form.Field/>. Formy abstracts all markup differences, allowing you to write unified and simple templates.

  Native validation

We didn't write a bunch of crappy regex. Browsers back to IE10 can validate any input type and standard validation constraint. Declare your constraints up front and let the browser do all the work.

🔐   Custom validation

Create your own form constraints and validation messages just as easily as the built-in ones, built off the standardized setCustomValidity api.

Simple example

Create an object of your form's initial state.

const form = {
   id: 'signupForm',
   onSubmit: Form.onSubmitFactory(data => console.log(data)),
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.setState({ form })),
   }, {
      name: {
         type: 'text',
         label: 'Name',
      },
      email: {
         type: 'email',
         label: 'Email',
      },
      password: {
         type: 'password',
         label: 'Password',
      },
      newsletterSignup: {
         type: 'checkbox',
         label: 'Signup for our newsletter?',
      },
   }),
};

this.state = { form };

Render the form.

const form = Form.getProps(this.state.form);

return(
   <Form.Component {...form}>
      <Form.Field {...form.fields.name}/>
      <Form.Field {...form.fields.email}/>
      <Form.Field {...form.fields.password}/>
      <Form.Field {...form.fields.newsletterSignup}/>
   </Form.Component>
);

HTML output:

<form id="signupForm">
   <label> Name <input type="text" name="name"> </label>

   <label> Email <input type="email" name="email"> </label>

   <label> Password <input type="password" name="password"> </label>

   <label>
      Signup for our newsletter?
      <input type="checkbox" name="newsletterSignup">
   </label>
</form>

Harder examples

In Formy you can define a field property's value as a computed function to resolve on render.

const form = {
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.updateForm(form)),
   }, {
      newsletterSignup: {
         type: 'checkbox',
         label: 'Signup for our newsletter?',
      },
      email: {
         type: 'email',
         label: 'Email',
         disabled: form => !form.newsletterSignup.checked,
      },
   }),
};

In this example, the email address input is disabled only if the checkbox isn't checked. Normally to achieve this you would need to add javascript outside of a form's HTML markup. However, you now have two sources of form state: your declarative form data written as HTML attributes and your imperative form data written in JS as hooks from input events.

Formy combines computed values and static values all in the same initial form object, keeping your data contained and easy to understand.

To create a computed value, pass in a function as a field property's value. On render, Formy calls the function and passes in the current form object and fieldKey string. This allows you to return a rendered value relative to all available data in the form.

Group radio buttons as an array in the radios property of a RadioGroup object. In this example, 'burrito' is the default selected value.

const form = {
   id: 'thingsYouLike',
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.updateForm(form)),
   }, {
      faveFood: {
         type: 'radiogroup',
         value: 'burrito',
         radios: [
            { label: 'Burrito', value: 'burrito' },
            { label: 'Pasta', value: 'pasta' },
         ],
      },
   }),
};

Render the RadioGroup as a single component.

const form = Form.getProps(this.state.form);

return(
   <Form.Component {...form}>
      <Form.Field {...form.fields.faveFood}/>
   </Form.Component>
);

This groups the radio buttons in a fieldset element, rendering the radio buttons in the order they're declared in the initial radios array.

<form name="signupForm">
   <fieldset>
      <label>
         Burrito
         <input type="radio" value="burrito" name="faveFood">
      </label>

      <label>
         Pasta
         <input type="radio" value="pasta" name="faveFood">
      </label>
   </fieldset>
</form>

Custom components are necessary for customizing a form beyond the default styles.

When a field is rendered, it's component is retrieved by accessing its componentLibrary property and retrieving the component associated with its type property.

You can retrieve a Form.Field's default component library like this:

Form.Field.defaultProps.componentLibrary

Here's an example of a custom component library extending Formy's default component library:

const customComponentLibrary = {
   ...Form.Field.defaultProps.componentLibrary,
   ...{
      text: props => (
         <label>
            <em>{props.label}</em>
            <input
               type={props.type}
               checked={props.checked}
               value={props.value}
               name={props.name}
               disabled={props.disabled}
               required={props.required}
               placeholder={props.placeholder}
               onChange={({ target: { value } }) => props.onChange({ value })}
            />
         </label>
      ),
   },
};

You can add a default componentLibrary property to every field in a form with the Form.fields function:

const form = {
   onSubmit: Form.onSubmitFactory(data => this.submitForm(data)),
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.setState({ form })),
      componentLibrary: customComponentLibrary,
   }, {
      text: {
         type: 'text',
         label: 'Whoah this is a seriously crazy custom component',
      },
      checkbox: {
         type: 'checkbox',
         label: 'This is a default component',
      },
   }),
};

If you have a super special field that you want to render with a custom component, while not setting a whole new component library for all fields, you can add the componentLibrary property to a specific field object in the Form.fields function:

const form = {
   onSubmit: Form.onSubmitFactory(data => this.submitForm(data)),
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.setState({ form })),
   }, {
      text: {
         type: 'text',
         label: 'Whoah this is a seriously crazy custom component',
         componentLibrary: customComponentLibrary,
      },
      checkbox: {
         type: 'checkbox',
         label: 'This is a default component',
      },
   },
};

Adding custom validation to your form fields follows this simple model:

  1. Declare your constraint. Ex: This input can't start with the letter 'z'.

  2. Add a validation message. Ex: "Names can't start with a 'z' sorry."

In Formy, custom validation looks like this:

const form = {
   fields: Form.fields({
      onChange: Form.onChangeFactory(form => this.setState({ form })),
   }, {
      name: {
         type: 'text',
         label: 'Enter your name',
         customValidity: Form.customValidityFactory(
            form => form.fields.name.value[0] !== 'z',
            "Names can't start with a 'z' sorry.",
         ),
      },
   }),
};

Your constraint function is just like all other computed properties. On render, Formy calls the function and passes in the current form object and fieldKey string, resolving to either an empty string (if valid) or the passed in validation message (if invalid).

You can stack built-in constraints with your custom constraints, so a field can have both be required and have to start with the letter 'z' like this:

{
   type: 'text',
   label: 'Enter your name',
   required: true,
   customValidity: Form.customValidityFactory(
      form => form.fields.name.value[0] !== 'z',
      "Names can't start with a 'z' sorry.",
   ),
}

Formy uses browser-native validation messages for its error states. If you want tighter control of your app's copy, you can override the standard validation messages by reimplementing native constraints as a customValidity function:

{
   type: 'text',
   label: 'Enter your name',
   // required: true, (reimplementing below)
   customValidity: Form.customValidityFactory(
      form => form.fields.name.value,
      "This field is required",
   ),
}

Form properties

A form object can have these properties:

Note: You can make any property a function that resolves to the appropriate type on render. See the "Computed properties" example above.

NameTypeDescription
fieldsObjectAn object of form fields
idStringThe id attribute of a form
nameStringThe name attribute of a form
onSubmitfunctionFunction to hook to a form's onsubmit event.

Field properties

A field object can have these properties:

Note: You can make any property a function that resolves to the appropriate type on render. See the "Computed properties" example above.

Core properties

NameTypeDefaultDescription
checkedBooleanfalseThe checked value of a field.
componentLibraryObjectFormDefaultComponentLibraryObject of react components to render form fields, with properties corresponding to all available type values.
nameStringThe field object's keyThe name value of a field. Defaults to the field object's key in the Form.fields function.
typeString'text'The type of field to render. Available default types: 'text', 'email', 'password', 'number', 'textarea', 'checkbox', 'radio', 'radiogroup'. Soon to be added: 'select'.
valueString''The value of a field.

Supported properties

NameTypeDescription
autocompleteStringThe autocomplete value of a field.
customValidityStringThe custom validation message of a field. An empty string means it's valid. A non-empty string means it's invalid.
disabledBooleanThe disabled value of a field.
labelStringThe label value of a field.
maxString OR NumberConstraint value for the max attribute
maxLengthNon-negative integerConstraint value for the maxlength attribute
minString OR NumberConstraint value for the min attribute
minLengthNon-negative integerConstraint value for the minlength attribute
onBlurFunctionFunction to hook to a field's onBlur event.
onChangeFunctionFunction to hook to a field's onChange event.
onFocusFunctionFunction to hook to a field's onFocus event.
onInvalidFunctionFunction to hook to a field's onInvalid event.
onMouseEnterFunctionFunction to hook to a field's onMouseEnter event.
onMouseLeaveFunctionFunction to hook to a field's onMouseLeave event.
patternStringConstraint value for the pattern attribute
placeholderStringAn input's placeholder value.
radiosArrayAn array of field objects to populate a radiogroup field. The type value of these radio objects doesn't need to be set since it's assumed to be radio.
requiredBooleanConstraint value for the required attribute. Not applicable for a radiogroup field.
rowsPositive integerThe rows value of a textarea. Not valid for any other field.
stepNumber or 'any'Constraint value for the step attribute

Other properties

You are welcome to add any properties you want to a Form or Field object – they're just objects! The only downside is they won't be type checked like the core or supported properties. Functions will be executed just like all computed properties.

API


Form

Library wrapper object.


Form.Component

Top level form component.

Props

A Form.getProps return value.

Returns

<form
   name={props.name}
   onSubmit={props.onSubmit}
>
   {props.children}
</form>

Form.Field

Container component used to structure a form.

Props

A field object of a Form.getProps return value.

Returns

<props.componentLibrary[props.type] {...props}/>

Form.customValidityFactory

Factory function for creating a custom validation message.

Parameters

NameTypeDefault valueDescription
constraintFunctionnoneYour custom validation logic. Passes in the current form object and fieldKey string, and expects a Boolean return value. true means valid, false means invalid.
validationMessageString'Invalid'The validation message to display if the custom validation is invalid.

Returns

NameTypeDescription
customValidityStringThe custom validity message. An empty string if valid, and validationMessage if invalid.

Form.fields

Helper function to generate an object of fields.

Parameters

NameTypeDescription
globalsObjectObject of values to assign to every field
fieldsObjectObject of fields

Returns

NameTypeDescription
fieldsObjectThe fields object, with every field now containing all the default field values, globals values, as well a name value with the value being the field object's key.

Example

Form.fields({
   onChange: event => {},
}, {
   firstName: {},
   lastName: {},
})

/*
{
   firstName: {
      checked: false,
      componentLibrary: {...},
      name: 'firstName',
      onChange: event => {},
      type: 'text',
      value: '',
   },
   lastName: {
      checked: false,
      componentLibrary: {...},
      name: 'lastName',
      onChange: event => {},
      type: 'text',
      value: '',
   },
}
*/

Form.getData

Function to get a form's data to be submitted.

Parameters

NameTypeDescription
formObjectForm state

Form.getProps

Function to get a form's props for rendering.

Parameters

NameTypeDescription
formObjectForm state

Form.onChangeFactory

Factory function to hook into an input's onChange event.

Parameters

NameTypeDescription
callbackFnFunctionFunction to call in an onChange event. When called, it passes in the new form state object as a parameter.

Form.onSubmitFactory

Factory function to hook into a form's submit event. Cancels the native submit event.

Parameters

NameTypeDescription
callbackFnFunctionFunction to call in a submit event. When called, it passes in the form's data object as a parameter.
1.0.15

3 years ago

1.0.14

3 years ago

1.0.13

4 years ago

1.0.12

4 years ago

1.0.11

4 years ago

1.0.10

4 years ago

1.0.9

4 years ago

1.0.8

4 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

0.1.0

9 years ago