Last active
August 25, 2018 22:11
-
-
Save bkonkle/038c99800281072fa645248ccca47584 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let compose = (second, first, errors) => { | |
let result = first(errors); | |
Array.length(result) > 0 ? result : second(result); | |
}; | |
let isString = input => Js.typeof(input) === "string"; | |
let check = (condition, error) => | |
Array.append(condition() ? [||] : [|error|]); | |
let checkExists = input => check(() => ! Js.Nullable.isNullable(input)); | |
let checkIsSome = input => check(() => Js.Option.isSome(input)); | |
let checkIsString = input => check(() => isString(input)); | |
let checkRegex = (re, input: Js.nullable(string)) => | |
check(() => | |
Js.Nullable.isNullable(input) ? | |
false : | |
Js.Re.test(input |> Js.Nullable.toOption |> Js.Option.getExn, re) | |
); | |
let checkLength = (length, input, error) => | |
checkExists(input, error) | |
|> compose( | |
check( | |
() => | |
Js.Nullable.isNullable(input) ? | |
false : | |
String.length(input |> Js.Nullable.toOption |> Js.Option.getExn) | |
> length, | |
error, | |
), | |
); | |
let checkArray = (test, error, array) => | |
check(() => Js.Array.isArray(array), error) | |
|> compose( | |
check( | |
() => | |
Js.Array.isArray(array) ? | |
Js.Array.reduce( | |
(valid, value) => | |
/* Short-circuit failure */ | |
if (! valid) { | |
false; | |
} else { | |
test(value); | |
}, | |
true, | |
array, | |
) : | |
false, | |
error, | |
), | |
); | |
let validate = (success, error, checks) => { | |
let errors = [||] |> checks; | |
let invalid = Array.length(errors) > 0; | |
let action = invalid ? error(errors) : success(); | |
(! invalid, action); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open ReasonReact; | |
[@bs.deriving jsConverter] | |
type accountType = [ | |
| [@bs.as "Parent"] `Parent | |
| [@bs.as "Caregiver"] `Caregiver | |
]; | |
[@bs.deriving jsConverter] | |
type serviceType = [ | |
| [@bs.as "Childcare"] `Childcare | |
| [@bs.as "Tutoring"] `Tutoring | |
]; | |
type specialNeed = { | |
label: string, | |
id: int, | |
}; | |
type specialization = { | |
label: string, | |
id: int, | |
}; | |
let unsafeGet = a => a |> Js.Nullable.toOption |> Js.Option.getExn; | |
[@bs.deriving jsConverter] | |
type state = { | |
email: option(string), | |
password: option(string), | |
displayName: option(string), | |
city: option(string), | |
accountType: option(accountType), | |
serviceType: option(serviceType), | |
specialNeeds: array(specialNeed), | |
specializations: array(specialization), | |
bio: option(string), | |
errors: array(string), | |
}; | |
type action = | |
| SetCredentials(string, string) | |
| SetProfile(string, string) | |
| SetAccountType(accountType) | |
| SetServiceType(serviceType) | |
| SetSpecialNeeds(array(specialNeed)) | |
| SetSpecializations(array(specialization)) | |
| SetBio(string) | |
| SetErrors(array(string)) | |
| SaveAccount; | |
let component = reducerComponent("AccountState"); | |
let make = children => { | |
...component, | |
initialState: () => { | |
email: None, | |
password: None, | |
displayName: None, | |
city: None, | |
accountType: None, | |
serviceType: None, | |
specialNeeds: [||], | |
specializations: [||], | |
bio: None, | |
errors: [||], | |
}, | |
reducer: (action, state) => | |
switch (action) { | |
| SetCredentials(email, password) => | |
Update({ | |
...state, | |
errors: [||], | |
email: Some(email), | |
password: Some(password), | |
}) | |
| SetProfile(displayName, city) => | |
Update({ | |
...state, | |
errors: [||], | |
displayName: Some(displayName), | |
city: Some(city), | |
}) | |
| SetAccountType(accountType) => | |
Update({...state, errors: [||], accountType: Some(accountType)}) | |
| SetServiceType(serviceType) => | |
Update({...state, errors: [||], serviceType: Some(serviceType)}) | |
| SetSpecialNeeds(specialNeeds) => Update({...state, specialNeeds}) | |
| SetSpecializations(specializations) => | |
Update({...state, errors: [||], specializations}) | |
| SetBio(bio) => Update({...state, errors: [||], bio: Some(bio)}) | |
| SetErrors(errors) => Update({...state, errors}) | |
| SaveAccount => SideEffects((_ => Js.log("Time to save!"))) | |
}, | |
render: self => | |
StateValidation.( | |
children({ | |
"state": stateToJs(self.state), | |
"setErrors": errors => { | |
let (valid, action) = | |
validate( | |
() => SetErrors(errors), | |
errors => SetErrors(errors), | |
checkArray(isString, "Non-string errors received.", errors), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setCredentials": | |
(. email, password) => { | |
let (valid, action) = | |
validate( | |
() => | |
SetCredentials(email |> unsafeGet, password |> unsafeGet), | |
errors => SetErrors(errors), | |
checkExists(email, "A valid email is required.") | |
|> compose( | |
checkRegex( | |
[%bs.re {|/\S+@\S+\.\S+/|}], | |
email, | |
"A valid email is required.", | |
), | |
) | |
|> compose( | |
checkLength( | |
7, | |
password, | |
"A password at least 8 characters long is required.", | |
), | |
), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setProfile": | |
(. displayName, city) => { | |
let (valid, action) = | |
validate( | |
() => SetProfile(displayName |> unsafeGet, city), | |
errors => SetErrors(errors), | |
checkExists(displayName, "A display name is required."), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setAccountType": | |
(. accountTypeJs) => { | |
open Js.Option; | |
let accountType = accountTypeFromJs(accountTypeJs); | |
let (valid, action) = | |
validate( | |
() => SetAccountType(getExn(accountType)), | |
errors => SetErrors(errors), | |
checkIsSome( | |
accountType, | |
"An account type of Parent or Caregiver is required.", | |
), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setServiceType": | |
(. serviceTypeJs) => { | |
open Js.Option; | |
let serviceType = serviceTypeFromJs(serviceTypeJs); | |
let (valid, action) = | |
validate( | |
() => SetServiceType(getExn(serviceType)), | |
errors => SetErrors(errors), | |
checkIsSome( | |
serviceType, | |
"A service type of Childcare or Tutoring is required.", | |
), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setSpecialNeeds": | |
(. specialNeeds: array(specialNeed)) => { | |
let (valid, action) = | |
validate( | |
() => SetSpecialNeeds(specialNeeds), | |
errors => SetErrors(errors), | |
checkArray( | |
isString, | |
"Non-string input received.", | |
specialNeeds, | |
), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setSpecializations": | |
(. specializations) => { | |
let (valid, action) = | |
validate( | |
() => SetSpecializations(specializations), | |
errors => SetErrors(errors), | |
checkArray( | |
isString, | |
"Non-string input received.", | |
specializations, | |
), | |
); | |
self.send(action); | |
valid; | |
}, | |
"setBio": (. bio) => self.send(SetBio(bio)), | |
"saveAccount": () => self.send(SaveAccount), | |
}) | |
), | |
}; | |
let fromJs = props => make(props##children); | |
let default = wrapReasonForJs(~component, fromJs); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment