<script type="module">
import shelacekFormica from 'https://cdn.skypack.dev/@shelacek/formica';
</script>
README
Formica
Preact forms made easy!
Formica allows you to build fairly complex forms declaratively in JSX. Nested forms and arrays
are supported out of the box. The library is trying to not re-invent the wheel so it combines
native forms API with controlled inputs (native isn't that bad). Validation is handled by the browser's constraint validation. Also, Formica has no dependencies and has a small footprint!
Formica consist of <Form /> and controls <FormGroup />, <FormArray />, <FormControl />
and <FormAction />. Form control can be also native form input like <input> or <select>.
<Form /> wrap native <form> element and expose form model to it's children.
<FormGroup /> represents the basic building block. It selects property from form model by
it's name prop and connect this submodel to actual controls, like <input>, but also another
<FormGroup />, <FormArray /> or <FormControl />.
<FormArray /> receive array and for every item render and connect its children. <FormArray />
can also receive the model object. In this case, it selects property by name prop like <FormGroup />
does.
<FormAction /> allow simple form model modifications, like adding/removing items of the array.
<FormControl /> is a helper component, that exposes disabled, touched and validity to enable
displaying of validation message. If you don't need validation or simple :invalid CSS
pseudo-class is enough, you can ignore <FormControl /> entirely.
Custom form controls
You can create custom controls. Formica map any vnode, that is enhanced
with asFormControlHigher-Order Components.
Custom form controls receive these props:
Prop
Type
Description
name
string ⎮ number (optional)
Name of connected model property.
value
any
Value of connected model property.
onChange
{ (event: OnChangeEvent<any>): void }
Value changes handler.
Please note: name prop can be also a numeric index in case, that the control is used directly
as an array item field.
Prop's onChange event can proxy native form input event or implement it's minimal subset:
export type OnChangeEvent<T> = Event | {
target: {
name: string | number; // same as props.name
value: T; // new mapped value
}
};
Minimal example of custom control:
import { h } from 'preact';
import { asFormControl } from "@shelacek/formica";
const FormInput = asFormControl(function({ children, ...attrs }) {
return (
<label>
{children}
<input {...attrs} />
</label>
);
});
Add/remove form array items
Let's say we have the following model:
type FormModel = { contacts: { email: string; }[]; };
Formica uses browser's native constraint validation, so you can use :invalid CSS pseudo-class
to highlight invalid fields and event.target.checkValidity() method in onSubmit handler.
If you want display invalid fields after blur (Validate on blur or keypress?), you can wrap
form field by <FormControl />, that apply CSS class .touched after the first blur.
Validation messages
You can use <FormControl /> also for displaying validation errors:
<FormControl name="age" id="name-field">
{({ touched, validity }) => (
<div>
<label>Age</label>
<input type="number" required min="15" />
{touched && validity.valueMissing && <span>This field is required</span>}
{touched && validity.rangeUnderflow && <span>Sorry, you must be at least 15 years old</span>}
</div>
)}
</FormControl>
Encapsulates <form> element and also wraps children into name-less <FormGroup />.
Properties
Prop
Type
Description
value
any
Form data (form model).
onChange
{ (value: any): void }
Form data changes handler.
onSubmit
{ (event: Event): void }
Form submit handler.
Any other passed properties will be added to <form> element.
FormGroup component
<FormGroup /> groups another form controls and connect them with the submodel.
Properties
Prop
Type
Description
disabled
boolean
Disable descendant controls.
name
string
Selected form model property to expose as submodel.
<FormGroup /> accepts standard vnodes or function as children. The function is called with an object
contains props passed to <FormGroup />, like name or data-index, disabled and value.
This can be useful if you need display index of array subform or some metadata carried
with subform value.
FormArray component
<FormArray /> iterate descendant controls by received submodel's array.
Properties
Prop
Type
Description
keyedBy
string
Name of form model property to use as <FormArray />'s item key.
disabled
boolean
Disable descendant controls.
name
string
Selected form model property to iterate. Array is expected if not specified.
FormControl component
<FormControl /> wrap and connect native <input>, <select> or <textarea>. It can also
associate label tag and expose some CSS classes and props for validation - namely disabled, touched
and invalid.
Properties
Prop
Type
Description
id
string
Id mapped to control's id and label's for.
class¹
string
Space-separated list of the classes.
disabled
boolean
Disable control.
name
string
Selected model property to connect with native input.
¹class and className are equivalent.
<FormControl /> accepts standard vnodes or function as children. The function is called with an object
contains disabled, touched and validity. Argument validity is ValidityState object,
others are booleans.
FormAction component
<FormAction /> can be used for simple form mutations and expose add and remove functions
to children render prop. Any of these functions can be called directly from onClick events
and the like.
Properties
Prop
Type
Description
name
string
Optional form submodel to attach.
item
{ (): void } | any
Item value or function that return value to append.
<FormAction /> accepts function as children which can takes object with 2 functions:
add function add item to selected (or parent) submodel array, or object.
remove function remove self from selected (or parent) submodel array, or object. Prop name
can specify key to remove.
This object also contains all props, that <FormAction /> received.
asFormControl HOC
Enhance any component with name, value and onChange props into form control.
It has only one argument: the wrapped component.
function asFormControl<P>(WrappedControl: ComponentFactory<P>): ComponentFactory<P>;