fakenickels / fakenickels-blog

fakenickels - Hi, I'm fakenickels
https://blog.fakenickels.dev
6 stars 1 forks source link

Vanilla ReSchema forms #3

Closed fakenickels closed 4 years ago

fakenickels commented 4 years ago

ReSchema is ReForm's companion for form validation. Even though they are kept in the same repository they have no need to be used always together.

Here we'll build a simple form validator using only ReSchema and React's hooks.

Demo

module ProfileLenses = [%lenses
  type state = {
    name: string,
    age: int,
  }
];

open ProfileLenses;

module Schema = ReSchema.Make(ProfileLenses);

let schema: Schema.Validation.schema(option(unit)) = {
  Schema.Validation.(
    Schema(
      string(~min=12, Name)
      + int(~min=18, ~minError="Too young", ~max=45, Age),
    )
  );
};

[@react.component]
let make = () => {
  let (profile, setProfile) = React.useState(_ => {name: "", age: 0});
  let (validation, setValidation) = React.useState(_ => None);

  let validateData = () => {
    setValidation(_ => Some(Schema.validate(profile, schema)));
  };

  let handleChange = (field, event) => {
    let value = ReactEvent.Form.target(event)##value;
    let newProfile = profile->ProfileLenses.set(field, value);

    setProfile(_ => newProfile);
  };

  <div>
    <input
      onBlur={_ => validateData()}
      placeholder="Nome"
      onChange={handleChange(Name)}
    />
    <input
      onBlur={_ => validateData()}
      placeholder="Idade"
      onChange={handleChange(Age)}
    />
    {switch (validation) {
     | None => React.null
     | Some(Valid) => <p> "Form is like totally valid"->React.string </p>
     | Some(Errors(errors)) =>
       <>
         <p> "Form is like not valid at all"->React.string </p>
         <ul>
           {errors
            ->Belt.Array.map(((_field, error)) => {
                <li> error->React.string </li>
              })
            ->React.array}
         </ul>
       </>
     }}
  </div>;
};

Of course that's for really simple forms, usually we want a much more fine grained display of the validations of a form.

Considerations about the API

the errors in Errors(errors) has a type of array((field, string)) which makes it a bit boring to extract a error for a specific field. One would do it like

let errorMsg = errors
 ->Belt.Array.getBy(((field, _)) == field == Name)
 ->Belt.Option.getWithDefault("No error")

Which is not very delightful at all. I'm thinking in probably export some helpers function from ReSchema itself to help handle those

let errorMsg = errors
 ->ReSchema.getErrorFor(Name)
 ->Belt.Option.getWithDefault("No error")

Which is a bit better and would allow for me to change the internal representation without my user noticing. What do you think?

Guide

yarn add reschema@2.0.2-alpha.0 && yarn add lenses-ppx -D

Then update your bsconfig.json bs-dependencies and ppx-flags entry

  "bs-dependencies": [
    ...,
    "reschema"
  ],
  "ppx-flags": [
    "lenses-ppx/ppx"
  ],

Install lenses-ppx and reschema