Informed is a set of components to reduce the work needed to create forms with react.
There are already many different form solution in the react ecosystem. Informed tries to
be different by minimizing boilerplate. Informed does not require decorated input
elements, either. Typescript definitions are provided for easier form refactoring.
Click here to view a demo.
<Form value={form}
preventAction={true}
onChange={(form) => this.setState({form})}>
<div>
<label htmlFor={form.firstName.name}>First Name</label>
<Field value={form.firstName}
debounce={50}>
<input type="text"
placeholder="First Name"
disabled={form.isSubmitted}
/>
</Field>
</div>
<div>
<label htmlFor={form.lastName.name}>Last Name</label>
<Field value={form.lastName}
debounce="onBlur">
<input type="text"
placeholder="Last Name"
disabled={form.isSubmitted}
/>
</Field>
</div>
<div>
<button disabled={!form.isValid || form.isSubmitted}
className="btn btn-primary"
type="submit">
submit
</button>
</div>
</Form>
Run examples
See the docs/examples/ directory for examples on how to use this library.
To run the examples locally:
npm install
npm start
and open your browser to http://localhost:8000/ (if it doesn't open automatically)
Goals
- Input elements should not be passed as props or wrapped in odd ways.
- There should be full control over look and feel using standard HTML props
- Un-opinionated about how form state is stored
- set the form based on props like with a redux-store sol'n
- or make use of a containing component's state
- Easily integrates custom components (just implement the value/onChange interface)
- Typescript integration
- limit the amount of unnecessary boilerplate needed for controlled elements
by automatically adding the needed value/onChange props
Form State Object
Every Form state object implements the BaseForm interface. There are several fields that
provide summary info about the form (is<*>).
export interface BaseForm {
/** true if the form was submitted. */
isSubmitted?: boolean;
/** true if any of the form fields have errors. If a field can only have an error if it is dirty. */
isError?: boolean;
/** true if there are no errors on any fields. Unlike isError, this checks fields regardless of their dirty flag. */
isValid?: boolean;
/** true if the form values are different from their original values */
isChanged?: boolean;
[k: string]: any;
}
Finally, each Field is stored as a property where the name of the Field is set
to an InputState object.
InputState
Each InputState object has several properties that get updated automatically
allowing to easily update the UI in response to events like form errors.
There may be some confusion about the difference between error and honestError.
For example, a that is required but initialized with an
empty value will immediately have an error. But we wouldn't really indicate an
error unless the user had attempted to modify the input and then left its value
blank. To avoid having to perform tests against the dirty property, the error
property is provided.
export interface InputState<T> {
/** property name of Field */
name: string;
/** Non-zero when the Field has an error (always 0 when dirty === false) */
error: number;
/** Non-zero when the Field has an error. Even when dirty === false) */
honestError: number;
/** value of Field*/
value: T;
/** True if the user has changed the form. Initial value is false */
dirty: boolean;
/** true if the current value is different (shallow comparison) from the original */
changed: boolean;
}
Field Usage
should be a direct wrapper around the input element or custom component
you want to use. If you need to use a
or similar to control positioning
this should be placed around
instead.
If you look at the inspected html you can see that does not create a wrapper
element that would impact your layout. Field will directly render
its child, so configure whatever props you want. But do not set value or onChange.
those props will be overwritten. Both of those are accessible on the
component so make use of them there.
Save Form State
The