Create a Form
Let's start with a minimal example, then we'll iterate over it in order to arrive to a final concrete example. In this way, we have the chance to understand how each piece connects to each other.
Minimal example#
Not exactly what you would use in a real project, but it gives an overall look:
Few things to note:
- We import
useFormanduseFieldfrom the package@mozartspa/mobx-form. - We wrap our component with
observer(), since we're using MobX. useForm()gives us back a stable reference to our form instance.useField()gives us back a reference to a specific field of our form. We pass it theforminstance, to make it know which form it should be bound to. It's required here, but in other examples we'll leverage the React Context.- With
onSubmit={form.handleSubmit}we let our form instance handle the onSubmit event. {...nameField.input}gives the input the necessary props to be a controlled input:name,value,onChange,onBlur.- With
{nameField.isTouched && nameField.error}we display the possible error only after the user touched the input. Anyway, in this case there's no input validation.
<Form /> component#
In the previous example we did not use any fancy component, just only HTML. It's a great thing, but we can do better using the <Form /> component provided by useForm(). Let's see how it looks:
The <Form /> component:
- is already bound to our
forminstance (after all, it's theforminstance that gives it to us); - handles
onSubmitandonResetevents automatically; - creates a React Context (FormContext) that children can use.
Note: the Form component is exposed by our form instance, we're not importing it.
<Field /> component#
In this case we have just one field, but thinking a more complex form we should go with many useField hooks. To make things easier, we can use the <Field /> component, exposed by the library. Let's use it:
The <Field /> component:
- is a thin wrapper around
useField; - requires a
nameprop with the name of the field; - requires
childrenprop to be a function the receives as input thefieldinstance (exactly the same returned byuseField); - uses the FormContext created by
<Form />to understand which form instance it belongs to.
Custom Input component#
Thanks to the <Field /> component, it's easy to create a custom input:
Here we have also added an age field of type number. The conversion between string to number is automatically managed by the library, because our custom input supports a type prop that we specified to be number for the field age. In this way, it will be rendered as <input type="number" value="36" name="age" /> in the HTML, letting the library to understand that the value should be converted to a number while updating the form values.
Using useField#
The custom component can be written using only the useField hook. Remember: <Field /> is just a thin wrapper around useField. Let's change it:
Few things to note:
- Our custom component is wrapped with
observer(), because we're accessing directly the values offield, and they are MobX observables. Without it, our component would not re-render every time something changes. - The
<Field />component didn't need theobserver()wrapper, because under the hood it was already using it. - We didn't pass the
forminstance to theuseFieldhook. Because, if not explicitely set,useFielduses the React Context created by<Form />to get access to theforminstance.
Using splitFieldProps#
In the previous example, our custom input component accepts only 3 props: name, label and type. It's enough in this case, because we are not using any option that useField() (or <Field />) can receive. Indeed useField() accepts, as second argument, a long list of options, many of them about validation (see useField API reference for more details).
In order to make our custom input component more versatile, we should make it accept a long list of options related to useField, that our component will pass to useField(). This is boring and error prone.
For this reason, we can use the splitFieldProps function which takes some props and splits them in:
nameof the fielduseFieldoptions- other unknown props
Here is how it should be used:
Few things to note:
- We updated the definition of
type InputPropsmerging it withFieldComponentProps: it contains all the options thatuseFieldcan accept (nameprop included). - We use
splitFieldPropsto split our props intoname,fieldOptionsandrest. - We pass
nameandfieldOptionstouseField() - We extract from
restthe 2 specific props of our component:labelandtype.
Debugging the state of the form instance#
During development it would be nice to know the internal state of our form instance. For this reason, there is a debug prop available on the <Form /> component.
Let's apply it:
If you run the code and edit the age field (setting it to 40 for example), you should see something like this below the form:
This is the state of our form, exposed by our form instance. age is actually a number (it does not have double quotes). Great!
Another thing: have you noticed that "touched" object? It contains the field names that triggered an onBlur event. It's a useful information in order to display the field error only when the user already interacted with the input. More on this in the Validation section.