shakacode / re-formality

Form validation tool for reason-react
https://re-formality.now.sh
MIT License
244 stars 35 forks source link

Dynamic forms options? #29

Closed liamcurry closed 5 years ago

liamcurry commented 6 years ago

First of all thank you @alexfedoseev for creating this, it's the best form library I've seen for ReasonML so far.

I'm working on a form that has a field that is an array of strings, where options can be dynamically added and removed. There should be between 2-30 items in the array, and each string should be between 1-140 characters.

This seems like it should be possible, but I'm wondering if you could provide some guidance/examples of how to accomplish this with Formality? Thanks again.

liamcurry commented 6 years ago

Ok so my current solution for this isn't pretty but it works. Maybe this can be improved, please let me know if you have suggestions:

Here is most of the relevant code:

  type value =
    | String(string)
    | NoValue;

  type field =
    | Title
    | Option(int)
    | AddOption
    | DelOption(int);

  let get = (field, state) =>
    switch (field) {
    | Title => String(state.title)
    | Option(index) =>
      state.options
      |. Belt.Array.get(index)
      |. Belt.Option.getWithDefault("")
      |. String
    | DelOption(_index) => NoValue
    | AddOption => NoValue
  };

  let baseValidators =
    Formality.(
      Validators.empty
      |> Validators.add(
           Title,
           {
             strategy: Strategy.OnFirstChange,
             dependents: None,
             validate: isRequired,
           },
         )
    );

  let validators =
    Formality.(
      Belt.Array.range(0, 100)
      |. Belt.Array.reduce(baseValidators, (result, i) =>
           result
           |> Validators.add(
                Option(i),
                {
                  strategy: Strategy.OnFirstChange,
                  dependents: None,
                  validate: isShorterThan(50),
                },
              )
         )
    );

  let update = ((field, value), state) =>
    switch (field, value) {
    | (Option(index), String(value)) =>
      state.options |. Belt.Array.set(index, value) |> ignore;
      state;
    | (AddOption, _value) =>
      state.options |> Js.Array.push("") |> ignore;
      state;
    | (DelOption(index), _novalue) => {
        ...state,
        options: state.options |> Js.Array.filteri((_o, i) => i != index),
      }
    | _ => failwith("Config.update function received bad input")
    };

/* Later on in the render function... */

               <ol>
                 (
                   form.state.options
                   |> Array.mapi((i, o) =>
                        <li key=(i |> string_of_int)>
                          <input
                            value=(
                              form.state.options
                              |. Belt.Array.get(i)
                              |. Belt.Option.getWithDefault("")
                            )
                            onChange=(
                              event =>
                                event
                                |> Formality.Dom.toValueOnChange
                                |. Form.String
                                |> form.change(Form.Option(i))
                            )
                          />
                          (
                            i >= 2 ?
                              <a
                                onClick=(
                                  _event =>
                                    form.change(DelOption(i), NoValue)
                                )>
                                ("[-]" |> ReasonReact.string)
                              </a> :
                              ReasonReact.string("")
                          )
                          (
                            switch (Form.Option(i) |> form.results) {
                            | Some(Invalid(message)) =>
                              <div className="failure">
                                (message |> ReasonReact.string)
                              </div>
                            | Some(Valid) =>
                              <div className="success">
                                ({j|✓|j} |> ReasonReact.string)
                              </div>
                            | None => ReasonReact.null
                            }
                          )
                        </li>
                      )
                   |> ReasonReact.array
                 )
               </ol>
               <a onClick=(_event => form.change(AddOption, NoValue))>
                 ("Add option" |> ReasonReact.string)
               </a>
alex35mil commented 6 years ago

Hey, thanks for kind words! TBH I haven't tried to implement it yet and I'd started poking around by changing value type to something like this:

type value =
  | String(string)
  | ArrayOfStrings(array(string));

If your solution works I'd leave it like this, for now. ATM I'm focused on shipping 1.0.0 of re-dnd and then I want to make one more attempt to couple value type w/ specific field (instead of making it abstract variant). I will keep in mind this use case during prototyping and try to come up w/ some built-in solution.

alex35mil commented 5 years ago

Let's continue in #43