[go: up one dir, main page]

Skip to content
This repository has been archived by the owner on Apr 13, 2021. It is now read-only.

iFixit/formy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Computed properties

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.

Radio buttons

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 component library

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',
      },
   },
};
Custom validation

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.

Name Type Description
fields Object An object of form fields
id String The id attribute of a form
name String The name attribute of a form
onSubmit function Function 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

Name Type Default Description
checked Boolean false The checked value of a field.
componentLibrary Object FormDefaultComponentLibrary Object of react components to render form fields, with properties corresponding to all available type values.
name String The field object's key The name value of a field. Defaults to the field object's key in the Form.fields function.
type String 'text' The type of field to render. Available default types: 'text', 'email', 'password', 'number', 'textarea', 'checkbox', 'radio', 'radiogroup'. Soon to be added: 'select'.
value String '' The value of a field.

Supported properties

Name Type Description
autocomplete String The autocomplete value of a field.
customValidity String The custom validation message of a field. An empty string means it's valid. A non-empty string means it's invalid.
disabled Boolean The disabled value of a field.
label String The label value of a field.
max String OR Number Constraint value for the max attribute
maxLength Non-negative integer Constraint value for the maxlength attribute
min String OR Number Constraint value for the min attribute
minLength Non-negative integer Constraint value for the minlength attribute
onBlur Function Function to hook to a field's onBlur event.
onChange Function Function to hook to a field's onChange event.
onFocus Function Function to hook to a field's onFocus event.
onInvalid Function Function to hook to a field's onInvalid event.
onMouseEnter Function Function to hook to a field's onMouseEnter event.
onMouseLeave Function Function to hook to a field's onMouseLeave event.
pattern String Constraint value for the pattern attribute
placeholder String An input's placeholder value.
radios Array An 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.
required Boolean Constraint value for the required attribute. Not applicable for a radiogroup field.
rows Positive integer The rows value of a textarea. Not valid for any other field.
step Number 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

Name Type Default value Description
constraint Function none Your custom validation logic. Passes in the current form object and fieldKey string, and expects a Boolean return value. true means valid, false means invalid.
validationMessage String 'Invalid' The validation message to display if the custom validation is invalid.

Returns

Name Type Description
customValidity String The custom validity message. An empty string if valid, and validationMessage if invalid.

Form.fields

Helper function to generate an object of fields.

Parameters

Name Type Description
globals Object Object of values to assign to every field
fields Object Object of fields

Returns

Name Type Description
fields Object The 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

Name Type Description
form Object Form state

Form.getProps

Function to get a form's props for rendering.

Parameters

Name Type Description
form Object Form state

Form.onChangeFactory

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

Parameters

Name Type Description
callbackFn Function Function 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

Name Type Description
callbackFn Function Function to call in a submit event. When called, it passes in the form's data object as a parameter.