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 exampleNot exactly what you would use in a real project, but it gives an overall look:
Few things to note:
- We import
useForm
anduseField
from 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 theform
instance, 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
form
instance (after all, it's theform
instance that gives it to us); - handles
onSubmit
andonReset
events 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
name
prop with the name of the field; - requires
children
prop to be a function the receives as input thefield
instance (exactly the same returned byuseField
); - uses the FormContext created by
<Form />
to understand which form instance it belongs to.
#
Custom Input componentThanks 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.
useField
#
Using 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
form
instance to theuseField
hook. Because, if not explicitely set,useField
uses the React Context created by<Form />
to get access to theform
instance.
splitFieldProps
#
Using 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:
name
of the fielduseField
options- other unknown props
Here is how it should be used:
Few things to note:
- We updated the definition of
type InputProps
merging it withFieldComponentProps
: it contains all the options thatuseField
can accept (name
prop included). - We use
splitFieldProps
to split our props intoname
,fieldOptions
andrest
. - We pass
name
andfieldOptions
touseField()
- We extract from
rest
the 2 specific props of our component:label
andtype
.
#
Debugging the state of the form instanceDuring 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.