Skip to content

Instantly share code, notes, and snippets.

@bkonkle
Last active August 25, 2018 22:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bkonkle/038c99800281072fa645248ccca47584 to your computer and use it in GitHub Desktop.
Save bkonkle/038c99800281072fa645248ccca47584 to your computer and use it in GitHub Desktop.
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);
};
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