Skip to content

Instantly share code, notes, and snippets.

@abradley2
Created August 8, 2023 12:15
Show Gist options
  • Save abradley2/733243645c5d5227caeee4b6ea76da23 to your computer and use it in GitHub Desktop.
Save abradley2/733243645c5d5227caeee4b6ea76da23 to your computer and use it in GitHub Desktop.
Applicative Form Validation in TypeScript
type State<s, a> = (state: s) => [s, a]
function pureState<s, a> (a: a): State<s, a | null> {
return (s: s) => [s, a]
}
function mapState<s, a1, a2> (fn: (action: a1) => a2 | null, state: State<s, a1 | null>): State<s, a2 | null> {
return (s) => {
const [nextState, action] = state(s)
if (action !== null) return [nextState, fn(action)]
return [nextState, null]
}
}
function applyState<s, a1, a2>(state2: State<s, ((a1: a1) => a2) | null>, state1: State<s, a1 | null>): State<s, a2 | null> {
return (s1) => {
const [s2, action] = state1(s1)
const [finalState, fn] = state2(s2)
if (action !== null && fn !== null) return [finalState, fn(action)]
return [finalState, null]
}
}
interface Form {
firstName: string | undefined;
lastName: string | undefined;
email: string | undefined;
}
interface FormErrors {
firstNameError?: string;
lastNameError?: string;
emailError?: string;
}
interface ValidForm {
firstName: string
lastName: string
email: string
}
const validForm =
(firstName: {firstName: string}) =>
(lastName: {lastName: string}) =>
(email: {email: string})
: ValidForm => Object.assign({},
firstName,
lastName,
email
)
const form = {
firstName: "a",
lastName: "a",
email: "a"
}
const withFirstName = mapState(validForm, validateFirstName(form))
const withLastName = applyState(withFirstName, validateLastName(form))
const withEmail = applyState(withLastName, validateEmail(form))
const result = withEmail({})
function validateFirstName ({firstName}: Form): State<FormErrors, {firstName: string} | null> {
if (typeof firstName === 'undefined' || firstName.trim() === "") {
return (s) => [ {...s, firstNameError: "This field is required" }, null ]
}
return (s) => [s, {firstName}]
}
function validateLastName ({lastName}: Form): State<FormErrors, {lastName: string} | null> {
if (typeof lastName === 'undefined' || lastName.trim() === "") {
return (s) => [ {...s, lastNameError: "This field is required" }, null ]
}
return (s) => [s, {lastName}]
}
function validateEmail ({ email }: Form): State<FormErrors, {email: string} | null> {
if (typeof email === 'undefined' || email.trim() === "") {
return (s) => [ {...s, emailError: "This field is required" }, null ]
}
return (s) => [s, {email}]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment