A Wrapper for React-Hook-Form using Contextes, allowing for extremely simple Form building, even with deeply nested form types
The main purpose for the development of this plugin was to create a easy to set up, plug and play system for Form development, while still allowing some degree of customization in terms of form styling
This readme will be updated further as we go along, as this is my first time writing documentation on Github, but most of the elements are for the most part self-explanatory, and the storybook page shows common usecases (Coming soon).
Wrap all form elements in the <Form>
component as shown
const onSubmit = (returnObjects: any) => new Promise<any>((resolve, reject) => {})
const defaultValues = {name: 'James'}
<Form onSubmit={onSubmit} defaultValues={defaultValues}>
{
... Inputs
}
</Form>
For inputs, the bare minimum needed is the name prop (label props are not required)
<Line name="name" /> // Line is used for default input elements, single lined basic inputs (but can be
// extended with the `type` prop)
Validation is now provided by the use of a Yup resolver, where the schema should be attached to the
as the propyupSchema
.
More options to come soon
For the
element the props areProp | Type | Use | Default |
---|---|---|---|
mode | 'onBlur','onChange','onSubmit','onTouched','all' | Validation Strategy before submission | onSubmit |
reValidateMode | "onChange" , "onBlur" , "onSubmit" | Validation Strategy after submission | onChange |
defaultValues | any | Partial of the form object schema | unedefined |
yupSchema | Resolver | Yup Schema Resolver | undefined |
context | FormContext | Form Context to allow deeply nested or complex form structures | FormContext |
criteriaMode | "firstError","all" | Show only First error or all errors for any field | "firstError" |
shouldFocusError | boolean | Should focus on the error element | true |
shouldUnregister | boolean | Unregisters an input value if the input is removed. If false, inputs value is kept even if the input is removed from DOM | false |
shouldUseNativeValidation | boolean | If the native validation should be used instead | false |
delayError | number | Delay the error message by x milliseconds | undefined |
elements | {[key: TListItem] : NewElement} | Changes the set element with a custom element. Similar to the as="" prop in other packages | undefined |
For any Input, the common props are
Prop | Type | Required(Y/N) | Description |
---|---|---|---|
name | string | yes | Input Name. It becomes the submitted outputs key for the value. can be dot notated line.data.first for nested inputs |
id | string | no | Id for the input |
defaultValue | T | no | A default value injector for the input. |
disabled | boolean | no | To disable or enable an input |
customClasses | { wrapperClassName?: string,labelClassName?: string,inputClassName?: string | undefined} | no |
style | React.CSSProperties | no | Custom CSS Properties for the InputElemWrapper
|
label | string | no | Input Label. |
reversedLabel | boolean | no | If the label positioning needs to be reversed |
helperText | string | no | To show a popup or some text to aid users in inputting data |
placeholder | string | no | Shows a placeholder for inputs |
noBorder | boolean | no | Removes borders on InputWrapper |
noLabel | boolean | no | To disabled the label from showing (Usually used in lists) |
buttons | { wrapper ?: {left ?: JSX.Element, right ?: JSX.Element, all ?: JSX.Element},left ?: {label: React.ReactNode, onClick: (value: T) => void, customButton ?: JSX.Element}[],right ?: {label: React.ReactNode, onClick: (value: T) => void, customButton ?: JSX.Element}[],} | no | If Provided, will render Any number of buttons on the left or right side of the input. Buttons have an onClick which outputs the current input value. The buttons on either side are wrapped in a flex div, but the wrapper can be changed using the wrapper prop, provide a wrapper.left ,wrapper.right for either side, or a wrapper.all for both sides. wrapper.all supplants either so take note. the buttons also have a customButton prop which allows for other Buttons to be used. The buttons will receive a name and value prop, corresponding to that of the input. |
inputWrapper | no | ComponentType | A Custom wrapper for positioning the input, labels, and helper/error texts. Useful when wanting to change the input style |
calculatedField | {isPromise ?: boolean, find ?: string[], calculate: (currentValue:T, currentName: string, foundFields: any[], allFields: any) => T | Promise} | no | Calculated fields allow automated filling up of the field based on some other value, for example, cost = unit price x quantity. the find argument is the other fields to watch for, and the calculation can return the value (if isPromise is undefined or false), or a Promise (if isPromise is true) if asynchronous calculations are needed (API Calls etc). |
externalStateSetter | (a:T) => void | no | Useful if you need to extract the value from the input while not within the context of the element. |
onInputChange | (inputValue:T, inputName: string, allValues: any, formMethods : useFormMethods ) => void | no | Helpful event handler that fires a function when your input changes. It ignores the first render (to prevent inputs that go from undefined -> null triggering the function). Any subsequent render is handled as usual. formMethods is an escape hatch for the methods of the react-hook-forms useForm , easier access for those that need to handle setting / getting form values programmatically. |
elements | ReactElement | Changes the set element with a custom element. Similar to the as="" prop in other packages | undefined |
The Line element are simply text inputs, using the input
element as its root, so HTML5 validation using the type attribute still works natively; eg: <Line type="email" {...props} /> validates as an email.
<Line name="some_input" label="Some Label" />
Lines are just textarea inputs, so at its core its just a textarea
element
<Lines name="some_input" label="Some Label" />
A WYSIWYG editor, powered by QuillJS. When the form is submitted you'll get the HTML as a plaintext string in the form object
<WYSIWYGEditor name="some_input" label="Some Label" />
A Datepicker powered by react-datepicker. Can use all props of react-datepicker as usual, just include them under the option prop; ie
<DatePicker name="some_input" label="Some Label" options={/*react-datepicker props*/} />
Currently two uploaders are available, a dashboard centered image uploader using Uppy, and a drag n drop system using react-dropzone. For Uppy, an endpoint is necessary, while for dropzone, it holds the File objects for submission at the end or for processing
The Dropzone uploader allows for the Uploading of multiple files, with a preview available for PDF / image / text / doc files
Use case :
<DropzoneUploader label="Example Uploader" name="uploader" />
<UppyDashboardUploader label="Example Uppy" name="uppy" endpoint="api.test.com/photos">
There are three types of lists available, Basic List, Template Lists, and Table Lists
A Basic List is generated using Bootstrap Row and Col elements. Just add the list of items via the item prop and you're good to go
<FormList
items ={[
{name: 'list_item_1', type:'text', label: 'List Item 1 Label' },
{name: 'list_item_2', type:'number', label: 'List Item 2 Label' },
{name: 'list_item_3', type:'datepicker', label: 'List Item 3 Label' },
{name: 'list_item_4', label: 'List Item 4 Label' },
/*Each object element is just the props you'd normally add to the Inputs as if they were standard elements (<Line {...props} />) etc*/
]}
showIndex /* This shows 1,2,3.... before each row of inputs*/
fixed /*If true list items cannot be added or removed. Suitable if editing a list injected with defaultValues*/
/>
Using the same method as the Basic List, you can also add in a template via the bodyTemplate prop. The template can be constructed via standard DOM or React elements, and add the prop "data-name" to link an element (a div would suffice) to an input (set via the items
props). To add an index, just add the data-index props and the index will be injected as a child of the element. For Adding or Removing rows, use data-add
and data-remove
respectively. Two props are injected into these elements, one is the onClick
handler that would handle adding/removing the rows. Another prop given is isEnd
prop, if true means the list cannot handle further clicks. You may use this to style the prop accordingly (active/disabled etc)
Tablelist operates the same as , with the exception that the inputs are generated as a table Element. a headerTemplate
and footerTemplate
prop is available to generate your own Header / Footer for the Table.
<TableList
items ={[
{name: 'list_item_1', type:'text', label: 'List Item 1 Label' },
{name: 'list_item_2', type:'number', label: 'List Item 2 Label' },
{name: 'list_item_3', type:'datepicker', label: 'List Item 3 Label' },
{name: 'list_item_4', label: 'List Item 4 Label' },
/*Each object element is just the props you'd normally add to the Inputs as if they were standard elements (<Line {...props} />) etc*/
]}
// headerTemplate / footerTemplates go into the <thead> and <tfoot> components respectively (if added)
headerTemplate={<tr>
<th></th>
<th>Item1</th>
<th>Item2</th>
<th>Item3</th>
<th>Item4</th>
<th></th>
</tr>}
/>
Following similar props to , this allows you to use a standard Form which after filling up, you may "Submit" via the (Add Row) button to fill up a table. The data given in the table is a summary of the inputs, so object data will usually be represented by their label
or value
key. Suitable if a form like structure better fits the data but batch filling is needed.
Powered by React-Select, most options that react-select
gives are preserved by adding the rsOptions
prop.
To change a select into a creatable select, just add the isCreatable
prop. If its boolean (ie, true) then it will add a new object as {value: x, label: x}. If its a function you will be able to asynchronously add the new entry in instead (perfect if you want to add the entry to DB first)
<Select
name="select_name"
label="Select Label"
options={[{value:1,label:'Option 1'}]}
isCreatable /*If added makes the input creatable*/
/>
There is two methods to load options for asynchronous react-select calls, that is using allLoad
or loadOptions
. Note that loadOptions
is required and thus some function should be included anyhow
Prop | Type | Description |
---|---|---|
loadOptions | (selectInput: string, callback: (A:TSelectOption)[] => void ) | Loads options using a callback |
allLoad | (selectInput:string, name: string, allValues: any, callback: (A:TSelectOption)[] => void ) | LoadOptions but with the current input name and all other values of the form. Useful if the input name cannot be determined beforehand (dynamic calls or list inputs) |
const handleLoadOption = (input:string, name: string, allValues: any, callback: (A:TSelectOption)[] => void ) => {
apiCall('test.com/getOptions', {inputString: input, name: name}).then((response) => {
if (response.status) {
callback(response.options)
}
})
}
<AsyncSelect
name="async_test"
label="Example Async Input"
loadOptions={(a,b) => (b([]))} /* If all Loads is used some function still
must be passed here. If allLoad is present
this function is then ignored*/
allLoad={handleLoadOption}
/>
To use checkboxes, similar to native use include a name prop and value prop.
Ditto on the Radiobox
Range inputs allow for handling a range of values (number usually) with a slider UI. Uses the react-range-slider-input
library
The prop sliderOptions
may be used as standard props of the react-range-slider-input
library
<Slider name="test_slider" label="Test slider" min={1} max={10} steps={2}/>
To add a custom element plug-and-play style, simply add it to the elements prop of the <Form>
element. You may use either InputChooser or replacing existing elements. You may use the interface CustomElementBaseInput
on your element for to aid in setting the props
const App = () => {
return <Form
elements={{
line: CustomElement
}}
>
<Line {...props} />
</Form>
}
// CustomElementBaseInput Interface outputs the methods of react-hook-form,
// as well as value, error, and an onchange that is specific to the input name
const CustomElement = (props : CustomElementBaseInput) => {
return <SomeNewInputElement
name={props.name}
value={props.value}
onChange={(a) => props.onChange(a)} /*NOTE. Give the onchange here a value and not an event!*/
/>
}
const App = () => {
return <Form
elements={{
custom1: CustomElem1,
custom2: CustomElem2
}}
>
<InputChooser {...propsforCustomElem1} type="custom1" /* The type here corresponds with the key of the element in the Forms elements props*/ />
<InputChooser {...propsforCustomElem2} type="custom2" />
</Form>
}
Currently more inputs are being planned out, including;
- [X] 1. Switches
- [ ] 2. Color-pickers
- [X] 3. Number Lines
- [ ] 4. Conversion Inputs (height / weight / etc)
- [ ] 5. Persist options
- [X] 6. Listed inputs (Add remove lines of inputs) Basic lists done
- 22 / 06 / 2023 - Added the onInputChange prop. For those using the calculatedField a minor breaking change will happen, now the 2nd argument is
current_name
representing the name of the input your calculated_field was called in. The foundFields and allFields are pushed further in.
- 2.5.1 - Issue noticed with InputListToTable, where it renders with edit mode already on. Fixed and now edit works except with WYSIWYG Editor, which does not clear upon adding a new entry. Will be looked at further
- 2.5.2 - Moved most value catches to a hook. Hook currently used internally but may be used externally, just make sure you are still within the confines of the context. The hook is called
useInputValAndError
, and it accepts one argument, which is the input name. It provides the value and error for the said input, as well as all the useform methods. An improvement somewhat made is that for InputListToTable, the input forms can be now validated before a new row is generated, but please keep in mind since we're using yup to add a clause to ignore validation if the fields are empty, as the form will still validate it together with the entire form on submission. Will work towards fixing this in the future. - 2.5.3 - Sometimes InputListToTable has an error due to a undefined / null value. A guard added to prevent that
This library uses storybookjs to test components and for examples. The code should be with the github page so feel free to try it out on your own. If there are any issues please reach out via github and I'll be happy to look into it. This library was made mostly for my convenience but its great if it's able to help out with other projects.