Mana form handles four aspects of forms: data management, layout, validation and submit.
- data management: get values show in each fields with correct controls and update values correctly
- layout: how each field is rendered: include control, label and error message
- validation: validate value restrictions & show errors
- submit: complete form filling
- When using mana form, an init value should be provided to useForm hook, which returns a form instance, then pass the instance to Form component:
// init value
const form = useForm({ a: "a", b: "b" })
<Form init={form}>...</Form>
The form instance identity is guaranted to be stable and won't change on re-renders.
- The form instance returned by useForm hook is useful when you want to get/set data outside of the form.
import {useForm} from '@mana-ui/form'
// init form
const form = useForm({a: 'a', b: 'b'})
<Form init={form}>...</Form>
// get/set value by form:
form.get() // {a: 'a', b: 'b'}
form.set('updated b', 'b') // set property b a new value
form.get('b') // returns 'update b'
- subscribe to value changes:
const form = useForm({ a: "a" })
form.listen((v) => console.log(v)) // log value when data changes
- useStore (DEPRECATED)
useStore is deprecated, use useForm instead
- Mana form provides default view components, and inject
value
andonChange
:
<Field name="f" label="F">
// render as <div><label for="f">F <input id="f" value="a"></label></div>
value is fetched by name or path, onChange is same as plain input
onChange prop
- customize components:
You can override control component by providing control prop a react element, Field will pass value
and onChange
props to this react element.
<Field
name="a"
label="A"
control={
<select className="custom-a">
<option value="a">a</option>
<option value="b">b</option>
</select>
}
/>
If your component don't receive value
or onChange
prop, you can use render prop function to render your component
<Field
name="b"
label="B"
control={({ get, set }) => {
return (
<input
type="checkbox"
checked={get()}
onChange={({ target: { checked } }) => {
set(checked)
}}
/>
)
}}
/>
- define form layout
Usually fields of a form have the same layout, you can define this layout by fieldRender
of Form.
<Form
fieldRender={({ Control, labelElem, id }) => (
<div className="field">
<label htmlFor={id} >{labelElem}</label>
<Control id={id} />
</div>
)}
>
{() => {
<>
<Field name="a" label="A" />
<Field name="b" label="B" />
</>
}}
</Form>
// reander as:
<div>
<div class="field" >
<label for="a">A</label>
<input id="a" value="A" />
</div>
<div class="field" >
<label for="b">B</label>
<input id="b" value="B" />
</div>
</div>
Or you can provide render to Field, which will overrides fieldRender from Form:
const customRender = (className) => ({ Control, labelElem, id }) => (
<div className={className}>
<label htmlFor={id}>{labelElem}</label>
<Control id={id} />
</div>
)
<Form
value={{ a: "A", b: "B" }}
fieldRender={customRender("field-render")}
>
{() => (
<>
<Field name="a" label="A" />
<Field name="b" label="B" render={customRender("field-b")} />
</>
)}
</Form>
// render as
<div>
<div class="field-render" >
<label for="a" />
<input id="a" value="A" />
</div>
<div class="field-b" >
<label for="b" />
<input id="b" value="B" />
</div>
</div>
You may define validators by validators
prop, and use validators by props other than Field used props:
<Form
validators={{
required: (v) => v === "" && "F is required",
}}
fieldRender={({ labelElem, Control, error }) => (
<div>
<label>{labelElem}</label>
<div>
<Control />
{error}
</div>
</div>
)}
>
{() => (
<Field
name="f"
label="F"
validators={{
maxLength: (v, max) => v.length > max && "F exceeds max length",
}}
required // required is not a Field used prop and it enables required rule defined in Form validators
maxLength={5} // as the same, maxLength enables the rule of Field validator with the param 5
/>
)}
</Form>
A validator is a function returns a message when a rule is not fullfilled. A validator is enabled when a prop same as the key. The validator function will be invoked with the field value as the first argument and the prop value as the second argument. Enabled validators on a Field will be called by chronological order defined on props.
You can decide how error is shown by fieldRender.
Form provides sumbit callback by children render prop, when it's called, all enabled validators get invoked, if anyone fails, onSubmit is skipped, otherwise onSubmit is called with form data.
<Form value={{ a: "x", b: "y" }} onSubmit={({value} => {})}>
{({ submit }) => (
<>
<Field name="a" label="A" />
<Field name="b" label="B" />
<button onClick={submit}>submit</button>
</>
)}
</Form>
FieldSet is used when your form have multi-layer data structure or you need validation of multi-fields.
// multi-layer form
<Form value={{ a: { b: "b", c: "c" } }}>
<FieldSet name="a">
<Field name="b" label="B" />
<Field name="c" label="C" />
</FieldSet>
</Form>
// multi-fields validation
<Form value={{ a: "a", b: "b" }}>
<FieldSet
validators={{
same: ({ a, b }) => a !== b && "A and B should be same",
}}
same
>
{({ error }) => (
<>
<Field name="a" label="A" />
<Field name="b" label="B" />
{error}
</>
)}
</FieldSet>
</Form>