@maif/react-forms is a React form builder with batteries included. You just have to define a form flow and a JSON schema to render stylish, highly customizable and fully managed forms.
@maif/react-forms use yup and react-hook-form to build its forms
If you think that an example will explain it better, you can go right now to our react-forms playground to discover a couple of examples with an inline editor.
npm install @maif/react-formsor
yarn add @maif/react-formsYou must define a form flow (this is just a javascript array which represents the rendering order of the form fields) and a schema (that defines all fields of your form with types or constraints for example) You can import the default stylesheet or use your own style by using default classname or passing a classname in your schema.
import { Form, type, format, constraints } from '@maif/react-forms'
import '@maif/react-forms/lib/index.css'
export const Example = () => {
const schema = {
age: {
type: type.number,
label: 'age',
placeholder: 'Your age',
help: "Just your age",
className: "input-number",
constraints: [
constraints.required("your age is required"),
constraints.min(18, 'You must be an adult'),
constraints.integer('half years are not accepted'),
]
},
name: {
type: type.string,
disabled: true,
label: 'name',
placeholder: 'your name',
constraints: [
constraints.required('Your name is required')
],
}
}
const flow = ['name', 'age']
return (
<Form
schema={schema}
flow={flow}
onSubmit={item => console.log({ item })}
/>
)
}-
type (required): the type of value. It provided by the imported object
type. It could be just string likestring,number,bool,object,dateorfile -
format: Over the type you can display a special format for the field. It is provided by the imported object
formator just a stringselect: display a react-select field with provided optionsbuttonsSelect: display a buttons group, drawn with the same options than the formatselect. You can add the propertyisClearableas boolean topropsto add the ability to unselect value. This format also works to display two buttons instead of a switch fortype.bool(add propstruelabel&falseLabelto customize buttons display).code: if the type isstring, display a code input (draw with react-ace) (add a props.setRef to get ace editor ref)singleLineCode: renders text input with syntax highlighting (draw with react-ace) (add a props.setRef to get ace editor ref)markdown: if the type isstring, display a markdown input. You can add buttons into toolbar by inject a JSX.element thanks to theactionsproperty frompropstaking on params the insert function.{ text: { type: type.string, format: format.markdown, props: { actions: (insert) => <button type="button" onClick={() => insert("foo")}>inject foo</button> } } }
textarea: if the type isstring, display a textareaemail: if the type isstring, display an email inputpassword: if the type isstring, display a password inputhidden: if the type isstring, add an hidden input in your formform: if the type isobject, display a form in your form draw with given schema and flow. The form drawn iscollapsable, you can choose which field will be visble or not by setting up thevisibleOnCollapseprops on subschema element properties.collapsablecan be a boolean or a function to render JSX (withrawValues,value&getValueparams){ collapsable: ({rawValue, value, getValue}) => <span>{value.firstname} {value.name}</span> }
-
array: boolean value to display multiple fields with "add" and "remove" buttons (
falseby default). You can defineaddabledefaultvalueto ensure the default value for the new input. -
createOption: if
selectformat is choosen,createOptionproperty is to render a Creatable component -
onCreateOption: if
selectformat is choosen,onCreateOptionproperty is a function called before new option creation{ onCreateOption: (option) => myFunction(option) }
-
isMulti: if
selectformat is choosen,isMultiproperty is to render a multiselect component -
defaultKeyValue: if
objectformat is choosen, this default key/value is set for all added entries -
visible: a boolean or functional boolean option to hide/display form field.
-
disabled: A boolean or functional boolean option to enable/disable form field
-
label: A string or functional string to labelize of form field (you can pass
nullto not render a label) -
placeholder: the placeholder of form field
-
defaultValue: A default value to fill field by default
-
help: the text display hover a help button
-
className: you can customize classnames of field,
-
style: to styling a field, you can provide a json object with css
-
onChange: a callback function to the value change.
({rawValues, value, setValue}) => if (value.length) { setValue('entry', false) }
-
onAfterChange: a callback function to the value change.
({entry, value, rawValues, previousValue, getValue, setValue, onChange, informations}) => { const otherEntryVal = getValue('otherEntry') console.debug({entry, value, rawValues, otherEntryVal}) setValue('anotherEntry', v + 1) const {path, parent, index} = informations console.debug({path, parent, index}) }
where :
- entry [string] is the updated entry of schema
- value [any] is the actual value of your entry
- previousValue [any] is the previous value of your entry
- rawValues [any] is the actual value of entire form
- getValue [function] is a function to get value of a form entry
- setValue [function] is a function to set value of a form entry
- onSave [function] is a function to update actual entry
- informations [Information] is an object to get informations about the actual entry (path, index and parent informations)
-
render: a function to completely custom the rendering of form field
({rawValues, value, onChange, error, setValue, parent}) => <input type="text" className="is-invalid" value={value} onChange={e => onChange(e.target.value)} />
You can use the
parentfield, to change a field of the current element (very useful when you have a list of forms, and you want to change a field different from your current element)({ setValue, parent }) => <input onChange={e => setValue(`${parent}.anotherField`, e.target.value)}>
-
itemRender: a function to completely custom the rendering of form field items (Just in case of
arrayis true)({rawValues, value, onChange, error}) => <input type="text" className="is-invalid" value={value} onChange={e => onChange(e.target.value)} />
-
props: a json object merged with default props. For exemple, if format
selectis setup, you can add all props to customize react-select -
options: An array of options for the select field (if format
selectis setup) -
optionsFrom: this property is setup to get options with a promise (if format
selectis selected). It offer multiple possibility to get its:- A url to fetch array of options for the select field. The array can be transform by the
transformerprovided - A promise returning an array, which can be transform by the
transformerprovided - a function returning a promise (see up) or directly an array of value
- A url to fetch array of options for the select field. The array can be transform by the
-
transformer: A function to transform options to a valid format for react select, by default the code try to do it himself.
{ transformer: (value) => ({label: value.name, value: value.age}) }
-
schema: a sub schema for the object value. this schema can contains constraints
-
conditionalSchema: an object to conditionnaly render an object value. Contains a
refto test as string and aswitchas an array of object which containsdefaultfor a boolean options to set default value,conditionas a function to run or just a value to test against ref value andschemaandflowto draw the object value.{ type: { type: type.string, format: format.select, defaultValue: "mammal", options: ["mammal", "fish"] }, place: { type: type.object, format: format.form, conditionalSchema: { ref: 'type', switch: [ { default: true, condition: ({rawValues, ref}) => ref === "mammal", schema: { continent: { type: type.string, }, country: { type: type.string, } } }, { condition: "fish", schema: { ocean: { type: type.string, }, deepness: { type: type.number, } } }, ] } } }
-
constraints: a JSON array of constraints. see constraints section
-
arrayConstraints: a JSON array of constraints apply to the entire array (use constraints to apply constraints to inner fileds of array). see constraints section
-
deps: In case of the entry need some form values, thestepwill be re-render after all change. To avoid some perf issues, it would be nice to declare a deps array to listen. The array must contains the root path
const schema = { one: { type: type.string }, foo: { type: type.object, format:format.form, schema: { foo: { type: type.string }, bar: { type: type.string }, } }, two: { type: type.string }, three: { type: type.string, deps: ['one', 'foo.bar'], visible: ({ rawValues }) => rawValues.one === '1' || rawValues.foo.bar === 'foo' }, }
-
item: In case of the entry is an array, some properties will be applied to the array step but not to the sub-items. In this particular case, you can use
itemproperty to add following properties (with same signature than the schema properties) to sub-items:- disabled
- visible
- label
- onChange
- onAfterChange
- render
- array
Some schema properties can be basic value or function which return basic value. This function has an object as param with these following properties :
- rawValues: the actual value of antire form
- value: the actual value of the actual entry
- error: the error of the entry (undefined if there is no error)
- informations: an object to get informations about the actual entry (path, key, index and parent informations)
- getValue: a function to get value of a form entry
- schema (required): the form schema
- flow (optional): the flow. The order of the schema fields by key (Just a javascript array of strings). Fields can be group on collapsable item, in this case, you can add an object with
label,flowand a booleancollapseproperty. Collapse item can draw inline form thanks to booleaninlineproperty - onSubmit (required): a function run on the form submission (in case if the form is valid )
- value (optional): default value
- inputWrapper (optional): A custom component to wrap all input of the form
- className (optional): A custom class name for the form
- footer (optional): a component to override the footer provided
{({ reset, valid }) => {
return (
<div className="d-flex justify-content-end">
<button className="btn btn-primary m-3" onClick={reset}>reset</button>
<button className="btn btn-success m-3" onClick={valid}>accept</button>
</div>
)
}}
- options (optional): an object to put some options for your form. see Form options for more informations.
- httpClient: a function to override the basic fetch used by react-forms to get async values (for optionsFrom)
httpClient = {(url, method) => fetch(url, {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-foo': 'bar'
}
})} - watch: a boolean to activate the automatic log of form value. A function can be set up to override the default logger.
- autosubmit: a boolean to activate the automatic run of the
onSubmitform properties on every change of values. - showErrorsOnStart: a boolean to display constraints messages, not in error color, when starting the form.
- actions: an object to parameter footer buttons key. By default just
submitbutton is displayed. Allow to change button label for the following actions:- reset
- cancel
- submit
- add
<Form
options={
autosubmit: true,
showErrorsOnStart: true,
actions={
reset: {
display: false (value by default)
label: 'reset' (value by default)
},
submit: {
display: true (value by default)
label: "save" (value by default)
}
}
}
/>Possible constraints are provided by import constraints from @maif/react-form or can be wrotes on json
By default all fields of the form are nullable (they can accept null or undefined value).
- mixed
constraints.required(message?:string)constraints.test(name: string, message?:string, test: (val: any) => boolean | Promise<boolean>)constraints.when(ref: string, test: (val: any) => boolean, then: any = [], otherwise: any = [])constraints.oneOf(arrayOfValues: any[], message?:string)constraints.ref(ref: any)
- string
- string or number
- number
- array
- date
- file
the following methods works for all type types of value.
Mark the value as required, which will not allow undefined or null as a value.
The provided message (or default message) will be display under form field as an error if the value is missing.
constraints.required("this field is required"){type: 'required', message: "this field is required"}Adds a test function to the validation chain. The test must provide a name, an error message and a validation function that takes in entry the current value and must return a boolean result. The test can return a promise that resolve a boolean result
constraints.test("name", 'not fifou', value => value === 'fifou'){type: 'test', name: "name", message: 'not fifou', test: value => value === 'fifou'}Adjust field constraints based on a sibling fileds given by ref. You can provide a matcher function with test. Then provides the true constraints and otherwise the constraints for failure condition
constraints.when(
'isBig',
(isBig) => !!isBig,
[constraint.count(5)],
[constraint.count(1)],){
type: 'when',
ref: 'isBig',
test: (isBig) => !!isBig,
then: [constraint.count(5)],
otherwise: [constraint.count(1)],
}Whitelist a set of values and display an error under field if the provided value is not contains in this set.
Values can also be a reference of value
constraints.oneOf(['foo', 'bar'], 'not foo or bar :('){type: 'oneOf', arrayOfValues: ['foo', 'bar'], message: 'not foo or bar :('}Some constraints accepts reference as property. This methods create a reference to another field. Refs are evaluated in the proper order so that the ref value is resolved before the field using the ref (be careful of circular dependencies!).
const schema = {
dad_age: { type: type.number},
son_age: {
type: type.number,
constraints: [
constraints.lessThan(constraints.ref('dad_age'), 'too old')
]
}
}const schema = {
dad_age: { type: 'number'},
son_age: {
type: 'number',
constraints: [
{type: 'lessThan', ref: { ref: 'dad_age' }, message: 'too old !'}
]
}
}the following methods works for string values. All methods for mixed are available.
Validate that the provided value matches an url pattern via regexp and display an error if the result id false.
constraints.oneOf(['foo', 'bar'], 'not foo or bar :('){type: 'oneOf', arrayOfValues: ['foo', 'bar'], message: 'not foo or bar :('}Validate that the provided value matches an email pattern via regexp and display an error if the result id false.
{type: 'email', message: 'not an email'}Validate that the provided value matches an uuid pattern via regexp and display an error if the result id false.
{type: 'uuid', message: 'not an uuid'}Test if value matche the provided regexp and display an error if the result id false.
constraints.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()@%&]).{8,1000}$/, errorMessage){type: 'matches', regexp: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()@%&]).{8,1000}$/, message: 'error'}the following methods works for string or number values. All methods for mixed are available.
Set the minimum value allowed and display an error if the provided value (for a number) or the length of the string is bigger. Provided value can be a number or a reference to a number
{type: 'min', ref: 1, message: 'too small'}Set the maximun value allowed and display an error if provided value (for a number) or the length of the string is smaller. value can be a number or a reference to a number
{type: 'max', ref: 5, message: 'too high'}The following methods works for number values. All methods for mixed are available.
The value must be a positive number and display an error if it's not.
{type: 'positive', message: 'positive please'}The value must be a negative number and display an error if it's not.
{type: 'negative', message: 'negative please'}The value must be aa integer number and display an error if it's not.
{type: 'integer', message: 'integer please'}Set the maximun value allowed and display an error if provided value (for a number) or the length of the string is smaller. value can be a number or a reference to a number
{type: 'lessThan', ref: 5, message: 'less please'}Set the minimum value allowed and display an error if provided value (for a number) or the length of the string is bigger. value can be a number or a reference to a number
{type: 'moreThan', ref: 5, message: 'more please'}the following methods works for basic types if the format is define to array. All methods for mixed are available.
Set the length of the array and display an error if it's different. value can be a number or a reference to a number
{type: 'length', ref: 5, message: 'this array must have 5 elements'}the following methods works for date values. All methods for mixed are available.
the following methods works for file values. All methods for mixed are available.
Whitelist a set of supported format for the provided file and display an error under field if the format is not contains in this set.
{type: 'supportedFormat', arrayOfValues: ['jpg', 'jpeg', 'png'], message: 'unsupported format'}Whitelist a set of unsupported format for the provided file and display an error under field if the format is contains in this set.
{type: 'unsupportedFormat', arrayOfValues: ['jpg', 'jpeg', 'png'], message: 'unsupported format'}Set the maximun value allowed for the file size and display an error if the size of provided file is bigger.
{type: 'maxSize', message: 'file size too big'}