UPDATE:
ReasonML + BuckleScript is now Rescript.
As the ecosystem has changed around those tools, this blog post is not accurate anymore.
In the last post, we finally finished the custom useForm
hook.
You can see the code on GitHub.
Use The Custom Hook
Let’s switch to our main form component: scr/Form.re
.
We need to connect the component to our custom hook.
/* src/Form.re */
[@react.component]
let make = (~formType) => {
let logger = () => Js.log("Form submitted");
let (state, formRules, handleChange, handleSubmit) =
UseForm.useForm(~formType, ~callback=logger);
// JSX here
};
So far, so good.
Display Form Validation Rules
Now let’s create a new component called FormErrors
, which will be in charge of displaying the list of form validation rules and their status.
We’ll create a nested module. The Reason Documentation recommends a flat project structure:
Try not to have too many nested folders. Keep your project flat, and have fewer files (reminder: you can use nested modules).
Inside src/Form.re
:
let str = ReasonReact.string;
module FormErrors = {
[@react.component]
let make = (~formRules: FormTypes.formRules) => // (A)
<div>
<ul>
{
Array.map(
rule =>
<li
key={rule.FormTypes.id |> string_of_int} // (B)
className={
rule.valid ?
"is-success help is-size-6" : "is-danger help is-size-6"
}>
<i className={rule.valid ? "fas fa-check" : "fas fa-times"} />
{" " |> str}
{rule.FormTypes.message |> str} // (B)
</li>,
formRules,
)
|> React.array
}
</ul>
</div>;
};
The make
function takes a labeled argument with the type FormTypes.formRules
(A
). The formRules
are defined in src/FormTypes.re
and we can access them with the dot operator.
We use Array.map
, a native Reason function, to loop over the Array. Unfortunately, it takes the input function as the first argument the array as the second argument.
Now the type checker doesn’t know the type of each rule
. That’s why we have to tell Reason the type again (see lines B
).
As an alternative, you could use BuckleScript’s Belt library, which offers a more familiar syntax for JavaScript developers. Belt.Array.map
takes the array as a first argument, and the function as the second argument.
We also have to convert types (B
) and convert the Array to a React.array. Reason’s type system is strict, and you have to jump through some hoops to get everything working.
Other than that, the component looks almost the same as a React component.
Connect Form And FormErrors
We now have to show the FormErrors
component inside the Form
component - the same as in normal React.
/* src/Form.re */
[@react.component]
let make = (~formType) => {
// form logic
let (state, formRules, handleChange, handleSubmit) = // (A)
UseForm.useForm(~formType, ~callback=logger);
<div className="section is-fullheight">
<div className="container">
<div className="column is-4 is-offset-4">
<h1 className="is-size-1 has-text-centered is-capitalized">
{formType |> str}
</h1>
<br />
{
switch (formRules) { // (B)
| [||] => ReasonReact.null
| _ => <FormErrors formRules />
}
}
<br />
// more JSX
The above code shows how we conditionally display the FormErrors
component (B
).
If we don’t have any formRules
we display ReasonReact.null
: we show nothing.
I’m sure you’ve previously run into the error that the array is undefined, and the map function can’t run.
We avoid this error by always initializing an array with validation rules. It’s either an empty array or an array containing login or registration rules.
If we have an array with rules, display FormErrors
, and hand down the formRules
that we received from the custom useForm
hook (A
).
Finished
And that’s the complete example. You can find the code on Github. I deployed the live demo to Firebase.
I originally wanted to deploy the demo to GitHub Pages but ran into client routing problems.
I’ll write a recap and thoughts about my learning process in a later post.