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.
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.
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.
Create your own form constraints and validation messages just as easily as the built-in ones, built off the standardized setCustomValidity api.
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>
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:
-
Declare your constraint. Ex: This input can't start with the letter 'z'.
-
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",
),
}
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. |
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.
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. |
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 |
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.
Library wrapper object.
Top level form component.
A Form.getProps
return value.
<form
name={props.name}
onSubmit={props.onSubmit}
>
{props.children}
</form>
Container component used to structure a form.
A field
object of a Form.getProps
return value.
<props.componentLibrary[props.type] {...props}/>
Factory function for creating a custom validation message.
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. |
Name | Type | Description |
---|---|---|
customValidity | String | The custom validity message. An empty string if valid, and validationMessage if invalid. |
Helper function to generate an object of fields.
Name | Type | Description |
---|---|---|
globals | Object | Object of values to assign to every field |
fields | Object | Object of fields |
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. |
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: '',
},
}
*/
Function to get a form's data to be submitted.
Function to get a form's props for rendering.
Factory function to hook into an input's onChange
event.
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. |
Factory function to hook into a form's submit
event. Cancels the native submit event.