@thinkmill/pragmatic-forms v2.0.0
Pragmatic Forms
A pragmatic approach to forms in React (Web and Native)
- Goals
- Example usage
- The Road to release
- API Documentation
configureForm: function(options:Object)initFields: Function(props): { [fieldName: string]: any }submit: Function(formData, props, formProps): Promisevalidate?: Function(formData, props, formProps): { [fieldName: string]: any }onSuccess?: Function(results, props, formProps): voidonError?: FunctiononChange?: Function(formData, props, formProps): voidonFirstInteraction?: Function(formData, props, formProps): void
- The
formPropform.isLoading: booleanform.isPristine: booleanform.submitError: anyform.submitResult: anyform.errors: { [fieldName: string]: any }form.hasErrors: booleanform.fields: Objectform.submit: Function(): voidform.reset: Function() :voidform.onSubmit: Function(event?: any) :voidform.onReset: Function(event?: any) :voidform.updateField: Function(name:String, value:any) :voidform.getInputProps: Function(options): InputPropsform.getFieldProps: Function(options): FieldPropsstate(Deprecated)actions(Deprecated)
- Reference material and prior art
Goals
- be simple
- be declarative
- be un-magical
- be performant
- be just react (and modern JS)
- work with YOUR state management system
- work in browser and react-native
Example usage
import { configureForm } from '@thinkmill/pragmatic-forms';
const RegistrationForm = configureForm({
initFields: () => ({
name: '',
email: '',
}),
validate: ({ name }) => {
const errors = {};
if (!name) errors.name = 'Please enter a name';
if (!name) errors.email = 'Please enter an email';
return errors;
},
submit: (formData) => {
return fetch('/registration', {
method: 'POST',
body: JSON.stringify(formData),
})
.then(res => res.json());
},
})(({ form }) => (
<form.Form>
{form.hasErrors &&
<div>
<p style={{ color: 'red' }}>Please correct the your input</p>
</div>
}
<input {...form.getInputProps({ name: 'name', type: 'text' })} />
<input {...form.getInputProps({ name: 'email', type: 'email' })} />
<button
type="submit"
disabled={form.hasErrors || form.isLoading}
>
Submit
</button>
</form.Form>
));API Documentation
When working with pragmatic-forms you will be interacting with either the
configureForm method or the form prop passed to your component.
configureForm: function(options:Object)
The pragmatic-forms module exports a single named function: configureForm. This method creates a configured Higher Order Component to wrap a form providing state and event handlers through a single prop named form.
configureForm accepts an options object and returns a method for creating a higher order component which will wrap your form to provide state and event handlers.
initFields: Functionsubmit: Functionvalidate?: FunctiononSuccess?: FunctiononReset?: FunctiononError?: FunctiononFirstInteraction?: Function
initFields: Function(props): { [fieldName: string]: any }
required
The initFields method will receive props as it's only argument. This method should return an object with key/value pairs that provide the default value for each form field in your form.
Tip: This is a good place to set a default value for a field to avoid the react warning "changing an uncontrolled input of type text to be controlled".
eg.
const withForm = configureForm({
initFields: (props) => ({
name: props.name || '',
email: props.email || '',
}),
...
});submit: Function(formData, props, formProps): Promise
required
The submit method will be called with formData, props and formProps and should return a promise.
eg. Trigger a graphql mutation using react-apollo
import { graphql, gql, compose} from 'react-apollo';
import { configureForm } from 'pragmatic-forms';
const query = gql`
mutation createUser (
$name: String!
$email: String!
) {
createUser (user: {
name: $name
email: $email
})
{
id
}
}
`;
export const MyForm = compose(
graphql(query),
configureForm({
initFields: () => ({ name: '', email: '' }),
submit: (formData, props) => {
return props.mutate({
variables: {
name: formData.name,
email: formData.email,
},
});
}
})
)(({ form }) => (
<form onSubmit={form.onSubmit}>
{'...'}
</form>
));validate?: Function(formData, props, formProps): { [fieldName: string]: any }
optional
The validate method receives formData, props and props and returns a map (Object) of errors keyed by the relevant fieldName.
eg.
const withForm = configureForm({
initFields: () => ({ email: '' }),
submit: (formData) => console.log(formData),
validate: (formData, props) => {
const errors = {};
if (!formData.email.includes('@')) {
errors.email = 'Please enter a valid email address';
}
return errors;
},
});Callbacks
Callbacks provide a way to act on the result of an operation or event within the form. All callbacks are called with an event specific value plus the props passed to the form and the formProps object.
onSuccess?: Function(results, props, formProps): void
optional
The onSuccess method is called after submit has resolved, the form state has been update and setState has been called.
It receives the result of the submit method, props and formProps as arguments.
onError?: Function(reason, props, formProps)
optional
The onError method is called after submit has rejected, the form state has been update and setState has been called.
It receives the rejection reason of the submit method props and formProps as arguments.
onReset?: Function(formData, props, formProps): void
optional
The onReset method is called on a form reset event after the form has been reinitialised, the form state has been update and setState has been called.
It receives the newly re-initialised formData, props and formProps as arguments.
onChange?: Function(formData, props, formProps): void
optional
The onChange method is called after the internal setState is complete when any form field has fired it's onChange or onValueChange event.
It receives the complete formData object, props and formProps as arguments.
onFirstInteraction?: Function(formData, props, formProps): void
optional
The onFirstInteraction method is called when a form fields onChange handler is triggered.
It receives the current formData, props and formProps.
The form Prop
aka formProps
The form prop provides access to the state and methods provided by pragmatic-forms inside your component. The same object is also passed as the last parameter to each of the configureForm methods (excluding initFields).
form.isLoading: boolean
true if the submit method has been called and the promise has not resolved or rejected. Otherwise false
form.isPristine: boolean
true until a change event is triggered on one or more of the forms inputs.
form.submitError: any
submitError is populated with the rejection reason if the form submit Promise is rejected.
form.submitResult: any
submitResult is populated with the resolved value (if any) once the forms submit Promise has resolved.
form.errors: { [fieldName: string]: any }
Key-value pairs giving the validation errors for each field by field name.
The value will be whatever was returned in the validation method.
form.hasErrors: boolean
true if the validate method returned an error object with at least one property.
form.fields: Object
Provides access to the form fields as they are stored internally in pragmatic-forms.
Each field will have the following shape:
[fieldName: string]: {
value: 'field value', // :any - whatever you put in here.
isDirty: false, // Boolean - has the field been modified
error: 'some error', // ?String - a field level error message (provided by the `validate` method)
}form.submit: Function(): void
Calling form.submit will trigger the submit handler directly.
Use cases:
- have a form which is not wrapped in a form tag
- trigger form submission programatically (eg. on a timer)
eg. Create a delete button
const withForm = configureForm({
initFields: (props) => ({ id: props.id }),
submit: ({ id }) => {
return fetch(`/item/${id}`, { method: 'delete' });
}
})
const DeleteBtn = withForm(({ form }) => (
<button
type="button"
onClick={form.submit}
disabled={form.isLoading}
>
Delete me
</button>
));form.reset: Function() :void
Calling form.reset will reset the form to it's original state.
form.onSubmit: Function(event?: any) :void
The submit event handler. This should be passed as onSubmit to a <form> component.
eg.
const withForm = configureForm({ ... });
const MyForm = withForm(({ form }) => (
<form onSubmit={form.onSubmit}>
...
<button type="submit">Submit</button>
</form>
));form.onReset: Function(event?: any) :void
The reset event handler. This should be passed as onReset to a <form> component. When the reset event is triggered, the form will be reset to it's original state.
eg.
const withForm = configureForm({ ... });
const MyForm = withForm(({ form }) => (
<form onReset={form.onReset}>
...
<button type="reset">Reset</button>
</form>
));form.updateField: Function(name:String, value:any) :void
Directly update the value of a field by name.
form.getInputProps: Function(options): InputProps
Returns an object with props which can be passed to an input component.
NOTE: For checkboxes it is important to provide the correct type. ie. 'checkbox'. This allows the onChange event handler to check the checked state of the input rather than reading the value.
options
name: stringtype?: stringDefaults to"text"value?:anyTODO: Requires explanation.checked?: booleanWhether a checkbox is checked. Defaults tofalseforceType?: 'string' | 'number' | 'boolean'undefined by default.
If forceType is true, pragmatic-forms will attempt to maintain the primitive type of the initial value of a field. For example, if initFields returns a boolean for the field isEnabled and onChange is called with a string it will attempt to convert that string back to a boolean. This can be helpful when your Input component converts the value field type to a string.
Currently forceType supports string, number and boolean.
InputProps
disabled: booleantruewhile the formisLoadingname: stringwhatever was provided inoptions.type: stringwhatever was provided inoptions. Defaults totextonChange: (event) => voida change handlerchecked: booleanOnly for acheckboxorradiovalueNot provided for acheckbox.
form.getFieldProps: Function(options): FieldProps
Takes the same options as form.getInputProps.
In addition to the props provided by form.getInputProps this method also returns props which can be used to show more information on a custom component.
error?: stringEither a string error message ornullif there is no error.isDirty: booleantruewhen the field has been modified.onValueChange: (value: any) =>a special change handler which accepts the value directly rather than via a change event.
Build and release
yarn build will compile the code which can then be either published or yarn linked if you are developing.
To release a new version of run yarn publish. This will run the build script and prompt for a new version number.
Reference material and prior art
Many of the ideas in here are not new. This is a list of some of the places I have taken inspiration from.