Skip to content

Commit

Permalink
Merge pull request #21 from scoville/use-array-instead-of-lists
Browse files Browse the repository at this point in the history
Use arrays instead of lists as they're cleaner and faster in our case
  • Loading branch information
gaku-sei authored Jan 14, 2021
2 parents ef72c21 + 98e5fc1 commit 776683f
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 61 deletions.
7 changes: 4 additions & 3 deletions example/App.res
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ let makeForm = Formidable.make(~values=module(Values), ~error=module(I18n.Error)
module Form = unpack(makeForm(~onSubmit=_values => (), ~onSubmitError=(_values, _errors) => (), ()))

open Validations

// Validations can be defined in an other module, and re-used easily
let requiredValidations = list{(#onChange, required)}
let requiredValidations = [(#onChange, required)]

let emailValidations = list{(#onChange, required->compose(email))}
let emailValidations = [(#onChange, required->compose(email))]

let passwordConfirmValidations = list{(#onChange, equals(Values.password))}
let passwordConfirmValidations = [(#onChange, equals(Values.password))]

module Child = {
@react.component
Expand Down
3 changes: 1 addition & 2 deletions example/Components.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ module Test = {

module Errors = {
@react.component
let make = (~value: list<I18n.Error.t>) => {
let make = (~value: array<I18n.Error.t>) => {
<div style={ReactDOMStyle.make(~color="red", ())}>
{value
->List.toArray
->Array.map((#error(name, _) as error) =>
<div key=name> {("Field has errors: " ++ I18n.translate(error))->React.string} </div>
)
Expand Down
37 changes: 17 additions & 20 deletions example/Validations.res
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
include Formidable.Validations
open Validator.Args
open Optic

let required = (
list{"required"},
({label, value}) =>
if value == "" {
#error(#error(("required", label)))
} else {
#ok(value)
let required = {
Description.names: list{"required"},
validator: ({label, value}) =>
switch value {
| "" => #error(#error(("required", label)))
| _ => #ok(value)
},
)
}

let emailRegEx = %re("/.+@.+/")

let email = (
list{"email"},
({label, value}) =>
if Js.Re.test_(emailRegEx, value) {
let email = {
Description.names: list{"email"},
validator: ({label, value}) =>
if emailRegEx->Js.Re.test_(value) {
#ok(value)
} else {
#error(#error(("email", label)))
},
)
}

let equals = lens => (
list{"equals"},
({label, value, values}) =>
if Lens.get(lens, values) === value {
let equals = lens => {
Description.names: list{"equals"},
validator: ({label, value, values}) =>
if lens.Optic.Lens.get(values) == value {
#ok(value)
} else {
#error(#error(("equals", label)))
},
)
}
38 changes: 20 additions & 18 deletions src/Formidable.res
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ module type Handlers = {

let onSubmit: option<values => unit>

let onSubmitError: option<(values, list<error>) => unit>
let onSubmitError: option<(values, array<error>) => unit>
}

module States = {
module Field = {
module Status = {
type t<'error> = [#pristine | #valid | #touched | #errors(list<'error>)]
type t<'error> = [#pristine | #valid | #touched | #errors(array<'error>)]
}

@bs.deriving(accessors)
Expand Down Expand Up @@ -77,8 +77,8 @@ module States = {
let hasErrors = fields => fields->Map.String.some(_ => Field.hasErrors)

let getErrors = fields =>
fields->Map.String.reduce(list{}, (acc, _, field) =>
field->Field.getErrors->Option.mapWithDefault(acc, errors => List.concat(acc, errors))
fields->Map.String.reduce([], (acc, _, field) =>
field->Field.getErrors->Option.mapWithDefault(acc, errors => acc->Js.Array2.concat(errors))
)

let reset = fields => fields->Map.String.map(Field.reset)
Expand Down Expand Up @@ -185,7 +185,7 @@ module type Form = {
~onBlur: ReactEvent.Focus.t => unit=?,
~onChange: 'value => unit=?,
~onFocus: ReactEvent.Focus.t => unit=?,
~validations: list<Validations.t<values, 'value, error>>=?,
~validations: array<Validations.t<values, 'value, error>>=?,
~disable: bool=?,
~key: string=?,
unit,
Expand All @@ -198,7 +198,7 @@ module type Form = {
"onBlur": option<ReactEvent.Focus.t => unit>,
"onChange": option<'value => unit>,
"onFocus": option<ReactEvent.Focus.t => unit>,
"validations": option<list<Validations.t<values, 'value, error>>>,
"validations": option<array<Validations.t<values, 'value, error>>>,
"disable": option<bool>,
} = ""

Expand All @@ -211,7 +211,7 @@ module type Form = {
"onBlur": option<ReactEvent.Focus.t => unit>,
"onChange": option<'value => unit>,
"onFocus": option<ReactEvent.Focus.t => unit>,
"validations": option<list<Validations.t<values, 'value, error>>>,
"validations": option<array<Validations.t<values, 'value, error>>>,
"disable": option<bool>,
}>
}
Expand Down Expand Up @@ -293,7 +293,7 @@ module Make = (
})

switch (Handlers.onSubmit, Handlers.onSubmitError, States.getErrors(fields)) {
| (Some(onSubmit), _, list{}) => onSubmit(values)
| (Some(onSubmit), _, []) => onSubmit(values)
| (_, Some(onSubmitError), errors) => onSubmitError(values, errors)
| _ => ()
}
Expand Down Expand Up @@ -366,7 +366,7 @@ module Make = (
~onBlur: ReactEvent.Focus.t => unit=?,
~onChange: 'value => unit=?,
~onFocus: ReactEvent.Focus.t => unit=?,
~validations: list<Validations.t<values, 'value, error>>=?,
~validations: array<Validations.t<values, 'value, error>>=?,
~disable: bool=?,
~key: string=?,
unit,
Expand All @@ -379,7 +379,7 @@ module Make = (
"onBlur": option<ReactEvent.Focus.t => unit>,
"onChange": option<'value => unit>,
"onFocus": option<ReactEvent.Focus.t => unit>,
"validations": option<list<Validations.t<values, 'value, error>>>,
"validations": option<array<Validations.t<values, 'value, error>>>,
"disable": option<bool>,
} = ""

Expand All @@ -392,18 +392,20 @@ module Make = (
let (isFocused, setIsFocused) = React.useState(() => false)

let validationNames =
props["validations"]->Option.mapWithDefault(list{}, validations =>
validations->ListExtra.flatMap(Validations.getName)
props["validations"]->Option.mapWithDefault([], validations =>
validations->ArrayExtra.flatMap(validation =>
validation->Validations.getNames->List.toArray
)
)

let hasValidation = name => validationNames->ListExtra.includes(name)
let hasValidation = name => validationNames->Js.Array2.includes(name)

let validate = React.useCallback1((validationContext, values) =>
props["validations"]->Option.mapWithDefault(#valid, validations =>
switch validations->List.keepMap(((strategy, (_, validation))) =>
switch validations->Array.keepMap(((strategy, {validator})) =>
if Validations.shouldValidate(~context=validationContext, ~strategy) {
switch validation({
Validations.Validator.Args.label: props["errorLabel"]->OptionExtra.alt(
switch validator({
Validations.Validator.Args.label: props["errorLabel"]->OptionExtra.or(
props["label"],
),
lens: props["lens"],
Expand All @@ -418,11 +420,11 @@ module Make = (
None
}
) {
| list{} => #valid
| [] => #valid
| errors => #errors(errors)
}
)
, List.toArray(validationNames))
, validationNames)

let validateAndUpdate = (validationContext, values) =>
modifiers.updateField(props["name"], field => {
Expand Down
12 changes: 3 additions & 9 deletions src/FormidableExtra.res
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
module ListExtra = {
let flatMap = (xs, f) => xs->List.reduce(list{}, (acc, x) => acc->List.concat(f(x)))

let rec includes = (xs, y) =>
switch xs {
| list{} => false
| list{x, ...xs} => x === y ? true : includes(xs, y)
}
module ArrayExtra = {
let flatMap = (xs, f) => xs->Array.reduce([], (acc, x) => acc->Js.Array2.concat(f(x)))
}

module OptionExtra = {
let alt = (option1, option2) =>
let or = (option1, option2) =>
switch (option1, option2) {
| (Some(_), _) => option1
| _ => option2
Expand Down
28 changes: 19 additions & 9 deletions src/FormidableValidations.res
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,36 @@ module Validator = {
type t<'values, 'value, 'error> = Args.t<'values, 'value, 'error> => Value.t<'value, 'error>
}

module Pair = {
type t<'values, 'value, 'error> = (list<string>, Validator.t<'values, 'value, 'error>)
module Description = {
type t<'values, 'value, 'error> = {
names: list<string>,
validator: Validator.t<'values, 'value, 'error>,
}
}

type t<'values, 'value, 'error> = (Strategy.t, Pair.t<'values, 'value, 'error>)
type t<'values, 'value, 'error> = (Strategy.t, Description.t<'values, 'value, 'error>)

module type Values = {
type t
}

let compose = ((name, validator), (name', validator')) => (
List.concat(name, name'),
field =>
let compose = (
{Description.names: names, validator},
{Description.names: names', validator: validator'},
) => {
Description.names: names->List.concat(names'),
validator: field =>
switch validator(field) {
| #ok(_) => validator'(field)
| #error(_) as error => error
},
)
}

let getStrategy = ((strategy, _)) => strategy

let getName = ((_, (name, _))) => name
let getNames = ((_, {Description.names: names})) => names

let getValidator = ((_, (_, validator))) => validator
let getValidator = ((_, {Description.validator: validator})) => validator

/* * Returns true if a validation should be performed in the given context.
If no context is provided, always returns true */
Expand Down

0 comments on commit 776683f

Please sign in to comment.