lenses-ppx icon indicating copy to clipboard operation
lenses-ppx copied to clipboard

Consider adding a way to get/list all the fields and iterate them ?

Open elnygren opened this issue 5 years ago • 3 comments

Sometimes when working with forms one might want to loop over all the fields and render any errors they have.

Perhaps this library could generate some kind utilities for this in addition to getters&setters of individual fields?

For example maybe I'd like to do this in onSubmitFail of reforms:

/** Handle failures detecred *before* onSubmit (mutation call).ShiftsListGraphQL
    Useful for displaying errors from local validations of ShiftForm. */
let onSubmitFail = ({state}: ShiftForm.onSubmitAPI) => {
  Js.log4("onSubmitFail", state.values, state.formState, state.fieldsState)

  let collectErrorString = field =>
    ShiftForm.getFieldState(~schema, ~values=state.values, ~field)
    ->Belt.Option.map((_, fieldState) =>
        switch (fieldState) {
        | ShiftForm.Error(s) => Some(s)
        | _ => None
        }
      );

  // build these into Js.Dict along their errors to render something to the user
  // eg; "there are errors in these fields"
  collectErrorString(Field(Name))
  collectErrorString(Field(BrandId))
  collectErrorString(Field(Is_draft))
  collectErrorString(Field(ShiftId))
  collectErrorString(Field(Shift_visibility))
  collectErrorString(Field(Contract_type))
  collectErrorString(Field(Matchmaking_industry))
  collectErrorString(Field(Matchmaking_category))
  collectErrorString(Field(Name))
  collectErrorString(Field(Description))
  collectErrorString(Field(LocationId))
  collectErrorString(Field(Custom_address))
  collectErrorString(Field(Languages))
  collectErrorString(Field(Requirements))
  collectErrorString(Field(Cover_photo))
  collectErrorString(Field(Schedule_type))
  collectErrorString(Field(Pricing_type))
  collectErrorString(Field(Rate_hourly_amount))
  collectErrorString(Field(Rate_fixed_amount))
  collectErrorString(Field(Times))
  collectErrorString(Field(Cnt))

};

however, the code can get quite tedious quite fast with larger forms.

Or maybe I should be approaching this problem differently?

elnygren avatar Apr 09 '19 12:04 elnygren

Actually this is already ReForm does internally, the trick is to iterate over the validation schema! https://github.com/Astrocoders/reform/blob/master/packages/bs-reform/src/ReFormNext.re#L57

To have a way to iterate over the list of GADTs directly is very cumbersome, see here https://discuss.ocaml.org/t/packing-gadt-constructors-in-iterable-data-structure/2678/4

fakenickels avatar Apr 27 '19 23:04 fakenickels

Sorry for the late response 😅 Thanks for using ReForm, FYI we are almost getting ready to launch ReFormNext as stable with a hooks API

fakenickels avatar Apr 27 '19 23:04 fakenickels

I've been using this pattern:

  let mergeWholeState =
      (
        formState,
        ~schema:
           ReSchema.Make(OnboardingProfileFormTypes.StateLenses).Validation.schema(
             option(ProfileFastForm.meta),
           ),
      ) => {
    open OnboardingProfileFormTypes.StateLenses;

    let Schema(schema) = schema;

    let changeField = field => {
      let currentState = currentWholeFormState->React.Ref.current;
      let newState = currentState->set(field, formState->get(field));
      currentWholeFormState->React.Ref.setCurrent(newState);
    };

    schema->Belt.Array.forEach(validator => {
      switch (validator) {
      | ProfileFastForm.Form.Validation.StringNonEmpty({field})
      | ProfileFastForm.Form.Validation.StringRegExp({field})
      | ProfileFastForm.Form.Validation.StringMin({field})
      | ProfileFastForm.Form.Validation.StringMax({field})
      | ProfileFastForm.Form.Validation.Email({field}) =>
        let currentState = currentWholeFormState->React.Ref.current;
        let newState =
          currentState->set(field, formState->get(field)->Js.String.trim);
        currentWholeFormState->React.Ref.setCurrent(newState);
      | ProfileFastForm.Form.Validation.IntMin({field})
      | ProfileFastForm.Form.Validation.IntMax({field}) => changeField(field)
      | ProfileFastForm.Form.Validation.FloatMin({field})
      | ProfileFastForm.Form.Validation.FloatMax({field}) =>
        changeField(field)
      | ProfileFastForm.Form.Validation.Custom({field}) => changeField(field)
      | ProfileFastForm.Form.Validation.NoValidation({field}) =>
        changeField(field)
      }
    });

    Hooks.OnboardingFormData.persist(currentWholeFormState->React.Ref.current)
    |> ignore;
  };

though the list of validators is pretty huge it's mostly boilerplate and if you are using ocamllsp you can use the destruct code action to generate it automatically for you

fakenickels avatar Aug 21 '20 12:08 fakenickels