Code Monkey home page Code Monkey logo

re-formality's Introduction

Formality

npm version build status license

Form validation tool for @rescript/react.

Features

  • Validation strategies
  • Async validations
  • Collections
  • Dependent fields
  • I18n compatible

ShakaCode

If you are looking for help with the development and optimization of your project, ShakaCode can help you to take the reliability and performance of your app to the next level.

If you are a developer interested in working on ReScript / TypeScript / Rust / Ruby on Rails projects, we're hiring!

Documentation

Examples

Contributing

See CONTRIBUTING.

Alternatives

  • ReForm
    Alternative form state management solution for ReasonReact apps.

License

See LICENSE.

Supporters

JetBrains ScoutAPM
BrowserStack Rails Autoscale Honeybadger

The following companies support our open source projects, and ShakaCode uses their products!

re-formality's People

Contributors

alex35mil avatar arnarthor avatar coobaha avatar jasoons avatar jfrolich avatar steinararnason avatar surajsingh62 avatar suzdalnitski avatar tmepple avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

re-formality's Issues

Dynamic forms

After poking around dynamic forms implementation I don't see a sane way how to implement it w/o code generation.

I haven't figured out neither public api or exact internal implementation, just a rough prototype of config dsl:

module NewArticleForm = [%form {|
  Title: string
  Body: string
  @nest Authors: {
    Name: string
    Url: string?
  }
|}];

I think from this I can get all data I need to generate proper field, state and some more internal types and lets to make dynamic forms possible but before going into implementation of this dsl I need to figure out internal implementation first. Also, it will require validators to be passed via props which is not a big deal I guess (comparing to the bloated and confusing config w/o dsl).

Not jumping into implementation yet and appreciate any feedback on it.

Problem building on Alpine

While trying to build with 4.0.0-beta.8 version on node:alpine in Docker container image, we ran into following errors:

sh: /node_modules/re-formality/ppx: not #found
We've found a bug for you!
/app/presentation/views/CommonViews.re
Error while running external preprocessor

We then tried with 4.0.0-beta.9 version and this error persisted.

Native apple arm support

Hi!
I'm in the process of trying to get everything on my team moved from rosetta to full native m1 support. Is there anything planned already? I'm happy to dedicate work hours or other resources to this if that is helpful. I've not done much with PPXs or any ocaml native builds of anything, but my primary focus on the team now is our tooling, and we use re-formality ppx heavily so I'm happy to dive in.

Optional `metadata` type

Sometimes, converting input to output requires some additional metadata. E.g. there is a select in UI with a list of items, each item is of Foo.t type which is a record {id: int, label: string}. Select's value is stringified id. In the output, there should be Foo.t. To get the output value in validator, array(Foo.t) is required.

It's achievable in the current state of things by:

  • storing in input Foo.t instead of stringified id
  • storing in input all foos: array(Foo.t) so it's available in validator

But neither looks nice to me.

Proposal: introduce an optional metadata type. If defined, it would be passed as a second argument to validators.

module Foo = {
  type t = {
    id: int,
    label: string,
  };
};

module Form = [%form
  type input = {foo: string};
  type output = {foo: Foo.t};
  type metadata = {foos: array(Foo.t)};
  let validators = {
    foo: {
      strategy: OnFirstChange,
      validate: (input, metadata) =>
        switch (
          metadata.foos
          ->Array.getBy(foo =>
              input.foo
              ->Int.fromString
              ->Option.mapWithDefault(false, id => id == foo.id)
            )
        ) {
        | Some(foo) => Ok(foo)
        | None => Error("Invalid foo")
        },
    },
  }
];

[@react.component]
let make = (~foos: array(Foo.t)) => {
  let form = Form.useForm(
    ~initialInput={foo: ""},
    ~metadata={foos: foos},
    ~onSubmit={...}
  );
  ...
};

Reorder items in collections

Currently, there's no way to change order of items in collections. There should be something like form.reorderAuthors exposed. API TBD.

Backwards compatibility in 4.0

I've been using the 4.0 beta in a new project, and really loving the PPX approach and how much less boilerplate I have to write - really nice job with that!

I've also got a project with a lot of forms that are currently on v3 API. I'd love to upgrade to v4 to start using the PPX, but don't want to have to upgrade everything in one shot. It looks like you've included the v3 API under FormalityCompat, but I wanted to check if you were planning on shipping that with the v4 final release and, if so, how long you anticipated supporting both APIs.

Thanks!

Why can't extract change and blur handler?

Hi, thanks for creating this library.

I noticed there is a common pattern like:

                   onChange=(
                     event =>
                       event
                       |> Formality.Dom.toValueOnChange
                       |> form.change(SignUpForm.Email)
                   )
                   onBlur=(
                     event =>
                       event
                       |> Formality.Dom.toValueOnBlur
                       |> form.blur(SignUpForm.Email)
                   )

So I tried to extract two functions to handle this:

             let handleChange = (field, event) =>
               event |> Formality.Dom.toValueOnChange |> form.change(field);
             let handleBlur = (field, event) =>
               event |> Formality.Dom.toValueOnBlur |> form.change(field);

            /* Usage */
             onChange=(handleChange(SignUpForm.Email))
             onBlur=(handleBlur(SignUpForm.Email))

Then it doesn't validate any more, why?

Global Form Validator

Is there a way to create a global form validator?

My use case is with a form that just requires any one of the fields to be completed for it to be valid. I can put the validation on a single field, but then the error message makes it look like that field is required.

My thought was making field in the validator optional. So you could do something like this:

let validator = {
      field: None,
      strategy: Strategy.OnFirstSuccessOrFirstBlur,
      dependents: Some([One, Two, Three, Four, Five]),
      validate: ({one, two, three, four, five}) =>
        switch (one, two, three, four, five) {
        | ("", "", "", "", "") =>
          Error("You must complete one of the fields.")
        | _ => Ok(Valid)
        },
    };

If there isn't a way now, do you have any suggestions on the best way to do it?

Thanks!

Dynamic forms options?

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.

`result` type

Before final 1.0.0 I want to discuss one more thing: result type.

type result('message) =
  | Valid
  | Invalid('message)
  | Optional;

From README:

You want to return Optional when optional field receives no value (e.g. value == ""). Valid and Optional states are explicitly differentiated since there's no reason to show success message/icon in UI when no value is provided.

Now it's not perfectly clear that both Valid & Optional are OK and only Invalid is show-stopper. It's also possible to mess something up by misusing Optional constructor. My proposal is to leverage Result type to make things perfectly clear:

type ok =
  | Valid
  | NoValue;

type validate = 'state => Belt.Result.t(ok, 'message);

let validate = state => switch (state.optional) {
  | "" => Ok(NoValue)
  | _ when !someCondition => Error("Nope")
  | _ => Ok(Valid)
};

Thoughts?

Side effects when validation fails?

I have this form that involves different screens, with routing between them etc. The state is global, and shared across screens (which are "dumb components").

I'm looking at re-formality as I think it could simplify a lot the current implementation (amazing work on the library!) but one thing I'm wondering is how to perform side effects on validation error.

In particular, I have to:

  • change the route
  • focus on the input that failed to validate

whenever validation fails.

I see there's only an onSubmit param to the useForm hook, would it be possible to add onFailedValidation as well? Or is there another way to implement this kind of feature with re-formality?

Record value of a field

There are 2 possible use-cases for record value. Both can use [@field.entity] attribute.

  1. Deserialization of set of fields into single type:
type input = {
  date: [@field.entity] date',
}
and date' = {
  day: int,
  month: int,
  year: int,
};

type output = {date: Js.Date.t};

type validators = {
  date: {
    entity: date' => result(Js.Date.t, message),
    fields: {
      day: {
        strategy: Strategy.t,
        validate: input => result(int, message),
      },
      ...
    }
  }
};

// Interface
form.updateDateYear(2020)
form.dateYearResult
form.dateResult
  1. Scoping set of fields. It might be useful when there is select in the form which affects which set of fields would be shown to a user.
type input = {
  select,
  a: [@field.entity] a',
  b: [@field.entity] b',
}
and select = A | B,
and a' = { fieldA: string }
and b' = { fieldB: string };

type output = {
  select,
  a: a',
  b: b',
};

I'm not sure if the second one is useful though since it's possible to implement it using current api. But if it is, then the question is how to name each.

Also, the prototype of the former is not really thought out so suggestions are welcome.

License?

There is no license file, but the project is listed as MIT in the package.json. Is that the correct license?

Thank you.

Passing form to child component

I may be missing something easy here, but I want to trigger a save from outside the form component. So, I'm creating an event that my form will listen for to trigger the save. In order to do that I want to have access to the form state in the didMount function. So I'm creating a wrapper component passing the form as an attribute so I can access form.state inside didMount. Please let me know if there is a better approach I'm missing.

My problem with this approach is that reason isn't detecting the type correctly and I can't seem to figure out how to access the type from the Formality namespace. To be clear, I have a component like this:

<FormContainer initialState={name: person.name}>
  ...{form => <FormFieldsWrapper form />}
</FormContainer>

The form attr on FormFieldsWrapper is what is not being typed correctly. This could be wrong, but it looks to me like the type is Formality__Form.Form. I don't see that type exposed in Formality.re.

Thanks!

Support for complex field types

Based on my understanding of how things are set up, it looks like you can only have fields of type string, as that's the type of Formality__Validation__value, and the get method must return that type. It'd be really great to support more complex types. For example: a group of checkboxes whose value is represented in state as an array of the string values of the selected options.

I noticed you have a "TODO" comment alongside that type definition so I'm guessing you've already thought of this. By using a variant there, I assume you'd seek to constrain the options available (string, int, bool, etc.)?

Collections nesting

Making collections nesting recursive is not trivial. But we should support at least 1 level of nesting.

FormId compare is not save

hi @alexfedoseev

I've noticed that Formality__Form.re uses Pervasives.compare:

module Make = (Form: Form) => {
  module FieldId =
    Id.MakeComparable({
      type t = Form.field;
      let cmp = Pervasives.compare;
    });
  ...
}

it will throw error in runtime if Form.field is complex type that has function or any other non-comparable type)

Maybe it should be something like this:

module type Form = {
  type field;
  type state;
  type message;
  type submissionError;
  let validators: list(Validation.validator(field, state, message));
  module FieldId:
    { type identity; type t = field; let cmp: Id.cmp(t, identity); };
};

module Make = (Form: Form) => {
  module FieldId = Form.FieldId;
  ...
}

Provide option to not reset form on submit

I can definitely understand resetting the form after a successful submit as the default behavior, but it'd be great if there were a way to prevent this in some cases. For instance, I'm working on a form that provides filter values for a list of data. On submit, the list gets refreshed with the new filter values... but my form resets so loses the user's input. I'd rather have it keep the same state after submitting.

I think it'd make sense to pass this option as a prop to the generated form component, and that way it could even change at runtime if desired. I tried implementing it a little in my own fork but was running into an issue of some kind. But, if that sounds like the right direction to you, I'd be happy to work on a PR for it. Thanks!

API overhaul

Major

  • Fast pipe API
  • Switch to Belt data structures
  • Pack validators into list instead of Map
  • Handle value using GADTs

Minor

Details

Fast pipe API

Change all functions to data-first style.

Switch to Belt data structures

Right now library uses OCaml std Map & Set. In context of bucklescript Belt is more appropriate choice. This change is also related to fast-pipe change.

Handle value using GADTs

Ok, this is the most interesting and controversial part and I very appreciate help/feedback on this.

In current implementation if you need your value type to be something else rather than string it's possible to define it as variant: see example. But by doing this there are a number of cases when it's possible to get into invalid state at runtime: e.g. here. Ideally, value type should be tied to field constructor but since these constructors are used as id's internally we can't add payload to it. From what I read, GADT seems like a way to go here and I started poking around w/ it. First step was to convert config to the new shape:

module LoginForm = {
  type field(_) =
    | Email: field(string)
    | Password: field(string)
    | Remember: field(bool);

  type state = {
    email: string,
    password: string,
    remember: bool,
  };

  type message = string;

  let get: type value. (field(value), state) => value =
    (field, state) =>
      switch (field) {
      | Email => state.email
      | Password => state.password
      | Remember => state.remember
      };

  let set: type value. ((field(value), value), state) => state =
    ((field, value), state) =>
      switch (field, value) {
      | (Email, value) => {...state, email: value}
      | (Password, value) => {...state, password: value}
      | (Remember, value) => {...state, remember: value}
      };

  let valueEmpty: type value. (field(value), value) => bool =
    (field, value) =>
      switch (field, value) {
      | (Email, value) when value == "" => true
      | (Password, value) when value == "" => true
      | (Email, _) => false
      | (Password, _) => false
      | (Remember, _) => false
      };

  let validators:
    type value.
      list(FormalityNext.validator(field(value), value, state, message)) =
    FormalityNext.[
      {
        field: Email,
        strategy: Strategy.OnFirstSuccessOrFirstBlur,
        dependents: None,
        validate: (value, _state) => {
          let emailRegex = [%bs.re {|/.*@.*\..+/|}];
          switch (value) {
          | "" => Invalid("Email is required")
          | _ as value when !value->Js.Re.test(emailRegex) =>
            Invalid("Email is invalid")
          | _ => Valid
          };
        },
      },
      {
        field: Password,
        strategy: Strategy.OnFirstBlur,
        dependents: None,
        validate: (value, _state) =>
          switch (value) {
          | "" => Invalid("Password is required")
          | _ => Valid
          },
      },
    ];
};

As you can see GADT requires some additional annotations. IMO not a big deal but it's not the primary issue right now.

The snippet above doesn't compile. The show stopper is that I can't find a way to pack GADT constructors into iterable data structure (like list or Map). W/o it I can't move further since it's critical to have the ability to store various field data inside something searchable & iterable.

Simplified version that illustrates the problem:

type t(_) =
  | S: t(string)
  | I: t(int);

let items: type a. list((t(a), a => bool)) = [
  (S, x => x == ""),
  (I, x => x == 0),
];

/*
  This has type:
    string t
  But somewhere wanted:
    a t

  The incompatible parts:
    string
    vs
    a
*/

Pack validators into list instead of Map

Simplifies public API:

Before:
module Validators =
  Formality.MakeValidators({
    type t = field;
  });

type validators =
  Validators.t(Formality.validator(field, value, state, message));

let validators =
  Formality.(
    Validators.empty
    |> Validators.add(
         Email,
         {
           strategy: Strategy.OnFirstSuccessOrFirstBlur,
           dependents: None,
           validate: (value, _) => {
             let emailRegex = [%bs.re {|/.*@.*\..+/|}];
             switch (value) {
             | String("") => Invalid("Email is required")
             | String(value) when !(emailRegex |> Js.Re.test(value)) =>
               Invalid("Email is invalid")
             | String(_) => Valid
             | _ => failwith("Email validator received bad input") /* sadly, as well */
             };
           },
         },
       )
    |> Validators.add(
         Password,
         {
           strategy: Strategy.OnFirstBlur,
           dependents: None,
           validate: (value, _) =>
             switch (value) {
             | String("") => Invalid("Password is required")
             | String(_) => Valid
             | _ => failwith("Password validator received bad input") /* sadly, as well */
             },
         },
       )
  );

After:

let validators = Formality.[
  {
    field: Email,
    strategy: Strategy.OnFirstSuccessOrFirstBlur,
    dependents: None,
    validate: (value, _state) => {
      let emailRegex = [%bs.re {|/.*@.*\..+/|}];
      switch (value) {
      | "" => Invalid("Email is required")
      | _ as value when !value->Js.Re.test(emailRegex) =>
        Invalid("Email is invalid")
      | _ => Valid
      };
    },
  },
  {
    field: Password,
    strategy: Strategy.OnFirstBlur,
    dependents: None,
    validate: (value, _state) =>
      switch (value) {
      | "" => Invalid("Password is required")
      | _ => Valid
      },
  },
];

Use re-debouncer

Originally, re-debouncer was extracted from here. Makes sense to use this package instead of current inlined code.

Update handlers API

Currently, field update handler takes function of type input => input. Mainly, b/c it guarantees that input value is not stale. But introduces unfortunate caveat:

// Runtime error
onChange={event => {
  form.updateEmail(input => {
    ...input,
    email: event->ReactEvent.Form.target##value,
  });
}}

This runtime error happens due to React's SyntheticEvent being pooled. Since callback gets triggered asynchronously, by the time it gets called, the event is already null'ed by React.

Options

  1. Leave it as is.
update[Field]: (input => input) => unit

onChange={event => {
  let value = event->ReactEvent.Form.target##value;
  form.updateEmail(input => {...input, email: value});
}}

Pros: guaranteed that input value is not stale.
Cons: possible runtime error due to event pooling.

  1. Change api so it takes just input:
update[Field]: input => unit

onChange={event => {
  form.updateEmail({
    ...form.input,
    email: event->ReactEvent.Form.target##value,
  });
}}

Pros: No event pooling issue.
Cons: possibility of staled input.

  1. Expose 2 handlers:
update[Field]: input => unit
update[Field]WithInput: (input => input) => unit

Pros: It's possible to choose a way to update input.
Cons: Bigger API surface and possibility to introduce both issues in user code.

Slow build time when exceeding 20 fields in form

Hi,

I'm using Re-Formality version 4.0.0.-beta-17 and noticed when exceeding more than 20 fields the build times goes up exponentially. In my project I need up to 50 fields and for that case it just doesn't even build.

I created a fresh project to test this, I only have one file implementing a simple form hook and added 20 fields to input, output and validate. I then added one field at a time to see the build time of the test project was growing and this was the result when reaching 25 fields:

image

Any idea why this is happening? I have used formality from earlier versions from you guys and I haven't had any problem with large forms before 🤔

I have added the test project as a zip so you can test this out for yourself
re-formality-test.zip

2 async fields

When 2 fields use [@field.async] then valid() function returns Some(true) even if one of them is invalid or even empty.

Generalize payload of `FormStatus.SubmissionFailed` constructor

I'm thinking of making payload of FormStatus.SubmissionFailed constructor generic, i.e. turn this:

module FormStatus = {
  type t('field, 'message) =
    | Editing
    | Submitting
    | Submitted
    | SubmissionFailed(list(('field, 'message)), option('message));
};

into this:

module FormStatus = {
  type t('error) =
    | Editing
    | Submitting
    | Submitted
    | SubmissionFailed('error);
};

I found that I never (literally, zero times) used current payload but do use domain specific error types all the time and put those in component state. With generic version it's still possible to use currently hardcoded type by defining it in user land but it would make it more flexible for other use-cases. I wonder how others use it (if use it at all).

Provide data on overall validation state of the form

Hey, it's me again! 👋 Got another enhancement I'd like to suggest:

It'd be great if the form interface provided some kind of overall validation status. Basically, true if each field is valid, false if any field isn't valid. I think I could theoretically gather up the results of calling form.status for each field, but that seems like a pain to have to do for each field, for each form. My use case here is it'd like to disable the submit button as a visual cue to the user if the form isn't in a valid state.

What do you think of this? Happy to help implement if you think it'd make a good addition. Thanks!

Record aliases

The requirement of having record definitions inlined might be inconvenient when some type already exists and used across the app.

It's not possible to avoid inlining since PPX would need type information to generate the proper code, but it can be handled somewhat better:

// input alias
module Form = [%form
  [@alias "Article.t"]
  type input = {
    title: string,
    body: string,
  }
];

// collection alias
module Form = [%form
  type input = {
    title: string,
    authors: [@field.collection] array(author),
  }
  [@alias "Author.t"]
  and author = {name: string}
];

An inlined record can be aliased to an existing type in generated code. It would still be required to duplicate record definition so PPX can parse the internal types but the generated code and the rest of the app would be dealing with the provided type instead of the one defined in the PPX'ed module.

So the generated code would be:

// input alias
module Form = {
  type input = Article.t;
};

// collection alias
module Form = {
  type input = {
    title: string,
    authors: array(author),
  }
  and author = Author.t;
};

It should be totally type-safe, but errors might be confusing when an inlined definition and actual type don't match (no way it can be ensured by PPX, unfortunatelly).

Any other drawbacks?

Updating form values from outside the form

Hi! Thanks for the great library!

Is there a recommended way to update the form values from outside the form?

For example, I have a form where a couple fields will be re-calculated on the backend when another field changes. I'd like to see the form update when its initialValues change. Similar to Formik's enableReinitialize property.

Example of server generated error after submitting?

Thanks for this great library!

I was looking at the LoginForm.re example for how to handle a server generated error message which is returned after submitting... such as "invalid login username or password". I see we should call notify.onFailure but I'm not sure how to inject the server error string so it's returned with form.results on a specific field and can be rendered similarly.

I guess I could process, store, and render the server error in state outside of re-formality but not sure if that's the right way to do it.

Split Formality module

Probably, makes sense to move functions from Formality module to own modules to reduce bundle size. E.g. if user doesn't use async stuff or collections, those wouldn't get into the final bundle.

Unless tree shaking works these days. Haven't checked what's up with that for quite awhile.

Request: ability to set validation errors in form submission callback

Our API validates requests as a whole using JSON schema and returns a list of issues that we would like to update the form state with. The schema includes more business-logic aspects that we wouldn't want to encode into the validators of the frontend. It would be very helpful if we could set the validation errors for each field in the form inside the submission callback. I'd be happy to contribute the change if you thought agreed with the approach, what do you think?

Form being submitted even when validations are failing

I have created the following form component:

module SearchUserForm = [%form
  type input = {
    mobile: string
  };
  let validators = {
    mobile: {
      strategy: OnSubmit,
      validate: (input) => {
        Js.log("validate called")
        switch (input.mobile) {
        | "" => Belt.Result.Error("Enter the Valid Mobile Number")
        | _ =>
          switch (ValidationHelper.MobileNumber.isValid(input.mobile)) {
          | false => Belt.Result.Error("Enter the Valid Mobile Number")
          | true => Belt.Result.Ok(input.mobile)
          }
        }
      }
    }
  }
]

[@react.component]
let make = (~caller_number: option(string), ~get_enterprise_user_info: string => unit) => {
  let form =
  SearchUserForm.useForm(
    ~initialInput={mobile: Belt.Option.getWithDefault(caller_number, "")},
    ~onSubmit=(output, _) => {
      Js.log("submit called " ++ output.mobile);
      get_enterprise_user_info(output.mobile);
    },
  );

  <form
    className="enterprise-user-search-container"
    onSubmit={(_) => {
      form.submit();
    }}>
    <p className="enterprise-user-search-heading"> {React.string("Fetch Caller Details")} </p>
    <div className="input-container">
      <FormInputV3
        label="Enter Caller Number"
        inputClassName="search-input-box"
        value={FormInputV3.String(form.input.mobile)}
        isDisabled=false
        fieldResult=form.mobileResult
        onChange={
          form.updateMobile((~target, _) => {
            mobile: target##value
          })
        }
      />
    </div>
    <div className="separator" />
    <button className="search-button"> {React.string("Search")} </button>
  </form>;
};

However, when we submit the form (using the submit button), the form is getting submitted even when the validation for the field returns an error. However, I am able to correct the behavior if I change the onSubmit in the form to:

    onSubmit={(event) => {
      event->ReactEvent.Synthetic.preventDefault;
      form.submit();
    }}>

Is there a mistake in the way I am using the ppx?

Clean up generated code

  • Drop field attributes from field type when saved in Metadata
  • In some cases (records, maybe something else) order of items is reversed

Could not resolve re-formality/ppx

I am on Windows 10 and I am trying to update old re-formality to the latest one.

  "name": "re-formality",
  "version": "4.0.0-beta.7",

When I do: bsb -make-world I get:

Could not resolve  re-formality/ppx in C:\Users\myuser\projectname
Fatal error: exception Failure("re-formality/ppx not found when resolving ppx-flags")

Async validation function for field required when creating the form definition

Issue

When using an async validator for a field, you have to have the function fully defined when you are writing out the form in the PPX. You are also not able to use any other fields inside of the form while executing the function. This is an issue when you require more data to finish the validation (say an OAuth token) since you can't use a partially applied function, nor reference a field in the form that would carry such data.

Solution 1

You can use module functors to get around this. You have a module functor that creates the form based on another module you pass in, which that form contains the function with all of the extra necessary data to complete the call. This gets out of hand fast though. You have to fully define the module types for both the input and the output which can be difficult, you have to store the created form module in something (like React.useState) so that you aren't creating a brand new form on each render, and you have to use first-class modules in order to be able to save the form.

This works right now and it does makes working with the form quite easy after a ton of upfront work using some things that are not well documented or known in the ReScript community. Not ideal IMO

Solution 2

Allow the PPX form to take in a function that is the of the same type as the function now (i.e. fieldOutput => Js.Promise.t(result(fieldOutput error)) via some custom named param when calling useForm.

e.g.

module Form = [%form
  type submissionError = Foo;

  type input = {
    email: [@field.async] string
  };

  type output = {
    email: Email.t
  };

  let validators = {
    email: strategy: OnFirst

  let validators = {
    email: {
      strategy: OnFirstSuccessOrFirstBlur,
      validate: input => input.email->Email.validate,
      validateAsync: FormArgument
    }
  };
];

[@react.component]
let make = (~validateEmailAsync) => {
  let _ = Form.useForm(~initialState={ email: "" }, ~onSubmit={(_,_) => ()}, ~validateEmailAsync);

  React.null;
};

Thoughts?

Complex forms

I'm trying to migrate plain reducer component that handles updates/validation of my form to re-formality. My form is complex and I'm having a hard time thinking about correct values/fields/updaters. Here is a type of the state:

type state = {
  name: string,
  parts: list(part)
}
and part = {
  image: string,
  title: string,
  subtitle: string,
  buttons: list(button),
}
and button =
  | UrlButton(url)
  | PluginButton(plugin)
and url = {
  id: string,
  caption: string,
  url: string,
}
and plugin = {
  caption: string,
  pluginId: string,
  instanceId: string,
};

For me, it's a really hard example. I want to have all functionality (like validation) available for even most nested values. Current API allows you to manage "one level deep" state really good but more complex state would require a lot of duplicated logic to update nested fields, a lot of fields, values etc.

Do you have any ideas on how we can improve re-formality API to better handle cases like this or how to approach this in application code using current API?

Request: Support for arm64

Requesting that re-formality work with arm64. User docker to build a container using the Docker desktop app for Apple M1 chip.

#22 59.73 error /build/client/node_modules/re-formality: Command failed.
#22 59.73 Exit code: 1
#22 59.73 Command: node ./postinstall.js
#22 59.73 Arguments:
#22 59.73 Directory: /build/client/node_modules/re-formality
#22 59.73 Output:
#22 59.73 re-formality-ppx does not support this platform :(
#22 59.73
#22 59.73 re-formality-ppx comes prepacked as built binaries to avoid large
#22 59.73 dependencies at build-time.
#22 59.73
#22 59.73 If you want re-formality-ppx to support this platform natively,
#22 59.73 please open an issue at our repository, linked above. Please
#22 59.73 specify that you are on the linux platform,
#22 59.73 on the arm64 architecture.
#22 59.73 fs.js:125
#22 59.73     throw err;
#22 59.73     ^
#22 59.73
#22 59.73 Error: ENOENT: no such file or directory, copyfile 'bin/re-formality-ppx-linux-arm64.exe' -> 'ppx'
#22 59.73     at Object.copyFileSync (fs.js:1782:3)
#22 59.73     at copyFileSync (/build/client/node_modules/re-formality/postinstall.js:49:8)
#22 59.73     at Object.<anonymous> (/build/client/node_modules/re-formality/postinstall.js:42:5)
#22 59.73     at Module._compile (internal/modules/cjs/loader.js:759:30)
#22 59.73     at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
#22 59.73     at Module.load (internal/modules/cjs/loader.js:628:32)
#22 59.73     at Function.Module._load (internal/modules/cjs/loader.js:555:12)
#22 59.73     at Function.Module.runMain (internal/modules/cjs/loader.js:826:10)
#22 59.73     at internal/main/run_main_module.js:17:11
#22 59.73 info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
#22 60.34 error Command failed with exit code 1.

Can't install re-formality on M1 mac

When i try to install re-formality via yarn i get this error message that tells me to file an issue here, so here I am:

re-formality-ppx does not support this platform :(

re-formality-ppx comes prepacked as built binaries to avoid large
dependencies at build-time.

If you want re-formality-ppx to support this platform natively,
please open an issue at our repository, linked above. Please
specify that you are on the darwin platform,
on the arm64 architecture.

Port solution for reason relay to reformality

I found that similar issue with GLIBC_2.29' not found which I am experiencing on Vercel for reformality ppx was fixed in https://github.com/zth/rescript-relay/pull/153/files following this discussion zth/rescript-relay#152 and it might be fixed easily for Vercel environment

Could you port this fix to reformality ppx?

I think this happens because the executables that are included in reason-relay are dynamically linked to newer versions of GLIBC than are available on the machines that Vercel/Netlify use. One possible solution is to statically link the executables so that they don't have a dependency on GLIBC. I'm planning on working on a PR for this today.


20:52:29.000 | FAILED: server/Server__User.ast
-- | --
20:52:29.000 | /vercel/path0/node_modules/re-formality/ppx: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /vercel/path0/node_modules/re-formality/ppx)


form.updateField [value type is automatically derived from the input type.]

Hey Fellas,

First of all thank-you for creating a wonderful way to handle forms in Reason-React. The ppx version is even better :)

The Issue:
The value type is automatically derived from the input type. This becomes an issue when my input type in the form state is something other than a string say integer

type input = {amount: int)};

 form.updateAmount(
              (input, value) => {...input, amount: value},
              event->ReactEvent.Form.target##value,
            )

The Use-case
1.) We type abc in the input field
2.) now this is tagged as an int and we can't validate this. since it is actually a string but reason is seeing it as int

Since the value comes from event->ReactEvent.Form.target##value,, will it make sense to keep it as a string and let the developer handle it the way he/she wants ?

Please find the complete code below. Also, you can see a working version in the repo here too.

Please let me know in case of any clarifications needed / or if we are missing something here.

Cheers
Prateek

module DummyForm = [%form
  type input = {amount: option(int)};
  let validators = {
    amount: {
      strategy: OnSubmit,
      validate: ({amount}) => {
        switch (amount) {
        | Some(amount) =>
          if (amount < 10) {
            Belt.Result.Error({js|Donations above the value of ₹ 10 are accepted|js});
          } else if (amount > 10000000) {
            Belt.Result.Error({js|Donations below the value of ₹ 1,00,00,000 are accepted|js});
          } else {
            Belt.Result.Ok(Some(amount));
          }
        | None => Belt.Result.Error("Please enter a donation amount")
        };
      },
    },
  }
];

[@react.component]
let make = (~initiate_payment: int => unit) => {
  let form =
    DummyForm.useForm(
      ~initialInput=DummyForm.{amount: None},
      ~onSubmit=(
                  output: DummyForm.input,
                  submissionCbs:
                    Formality.submissionCallbacks(
                      DummyForm.input,
                      DummyForm.submissionError,
                    ),
                ) => {
        switch (output.amount) {
        | Some(value) => {
          initiate_payment(value)
        }
        | _ => ()
        };
        submissionCbs.reset();
      },
    );

  <form onSubmit={form.submit->FormalityCompat.Dom.preventDefault}>
    <div>
        <label htmlFor="amount">
          "Donation Amount"->React.string
        </label>
        <input
          id="amount"
          type_="text"
          value={switch(form.input.amount){
            | Some(amount) => string_of_int(amount)
            | None => ""
          }}
          disabled={form.submitting}
          onChange={event =>
            form.updateAmount(
              (input, value) => {...input, amount: value},
              event->ReactEvent.Form.target##value,
            )
          }
        />
    </div>
    <hr />
    <button>
      <span> {React.string("Donate")} </span>
    </button>
  </form>;
};


Thank you!

This is a very well thought out library on form validation. Learning a lot about Reason by reading through the documentation and code. Thanks for creating!

Warnings 40 and 42 with onSubmit callback

If I have code like this:

let form =
  useForm(
    ~initialState=initialState,
    ~onSubmit=(state, form) => {
      /* do something with state here */
      form.notifyOnSuccess(None);
      form.reset();
    },
  );

I get these warnings:

src/App.re|180 col 16 warning| Warning 40: notifyOnSuccess was selected from type Formality__FormWithId.Validation.submissionCallbacks. It is not visible in the current scope, and will not  be selected if the type becomes unknown.
src/App.re|180 col 16 warning| Warning 42: this use of notifyOnSuccess relies on type-directed disambiguation, it will not compile with OCaml 4.00 or earlier.
src/App.re|181 col 16 warning| Warning 40: reset was selected from type Formality__FormWithId.Validation.submissionCallbacks. It is not visible in the current scope, and will not  be selected if the type becomes unknown.
src/App.re|181 col 16 warning| Warning 42: this use of reset relies on type-directed disambiguation, it will not compile with OCaml 4.00 or earlier.

I can get around it by doing this:

form.Formality__Validation.notifyOnSuccess(None);
form.Formality__Validation.reset();

Which is kind of ugly, but I can also do this:

open! Formality__Validation;
form.notifyOnSuccess(None);
form.reset();

Note that using open instead of open! will also cause this warning:

src/App.re|177 col 40 warning| Warning 45: this open statement shadows the label reset (which is later used)

Unless I'm missing something, all of these options feel hacky and aren't intuitive. I'm not familiar with the internals of re-formality, but my general suggestions are:

  • Using a shorter module namespace for those record fields.
  • Adding a note about it to the documentation.

Beta 11 can’t build on Netlify

I upgraded to Beta 11 (and upgraded reason-react to @rescript/react) and the build now fails when deploying to Netlify.

A variation of this error appears for every file in my project:

bsb: [5/11] assets/Contact_Form.iast
1:32:55 PM: FAILED: assets/Contact_Form.iast
1:32:55 PM: /opt/build/repo/node_modules/re-formality/ppx: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by /opt/build/repo/node_modules/re-formality/ppx)

  We've found a bug for you!
  /opt/build/repo/assets/Contact_Form.resi
  
  Error while running external preprocessor
Command line: /opt/build/repo/node_modules/re-formality/ppx '/tmp/ppxd38401Contact_Form.resi' '/tmp/ppxd2563aContact_Form.resi’

Reverting to Beta 10 fixes the errors.

I can’t reproduce this on my local MacOS machine. I also tried some basic debugging (clearing Netlify cache, removing node_modules) but it didn’t make a difference.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.