Skip to content

Instantly share code, notes, and snippets.

@scarf005
Created June 10, 2023 22:26
Show Gist options
  • Save scarf005/f9b7830c6630c81beb453538ab244114 to your computer and use it in GitHub Desktop.
Save scarf005/f9b7830c6630c81beb453538ab244114 to your computer and use it in GitHub Desktop.
unnecesarily complex useform with only string input
export const mapEntries = <T, U>(
obj: Record<string, T>,
fn: (entry: [string, T]) => [string, U],
) => Object.fromEntries(Object.entries(obj).map(fn))
import { ChangeEvent, FormEvent, useState } from "react"
import {
UseInputValidation,
useInputValidation,
useInputValidations,
ValidationState,
} from "./useInputValidation.ts"
import { mapEntries } from "./mapEntries.ts"
export type UseInput = {
value: string
handle: (e: ChangeEvent<HTMLInputElement>) => void
state: ValidationState
}
export const allValid = (props: UseInput[]) =>
props.every(({ state: { type } }) => type === "valid")
export const useInput = () => {
const [value, set] = useState("")
const { state, validate } = useInputValidation()
const handle = (e: ChangeEvent<HTMLInputElement>) => {
validate(e)
set(e.target.value)
}
return { value, handle, state }
}
export const useForm = <const T extends readonly string[]>(
keys: T,
) => {
const validations = useInputValidations(keys)
const [vals, set] = useState<Record<string, string>>(
Object.fromEntries(keys.map((key) => [key, ""])),
)
const result = mapEntries(vals, ([key, val]) => {
// @ts-expect-error: to much work to zip with type safely
const { validate, state } = validations[key] as UseInputValidation
return [
key,
{
value: val,
state,
handle: (e: ChangeEvent<HTMLInputElement>) => {
validate(e)
set({ ...vals, [key]: e.target.value })
},
},
]
})
const values = mapEntries(vals, ([key, val]) => [key, val]) as {
[K in T[number]]: string
}
const allValid = Object.values(result).every(
({ state: { type } }) => type === "valid",
)
const handleSubmit =
(fn: (v: typeof values) => void) => (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (allValid) fn(values)
}
return {
inputs: result as { [K in T[number]]: UseInput },
allValid,
values,
handleSubmit,
}
}
import { ChangeEvent, useState } from "react"
import { mapEntries } from "./mapEntries.ts"
export type ValidationState =
| { type: "valid" }
| { type: "invalid"; message: string }
export type UseInputValidation = {
state: ValidationState
validate: (e: ChangeEvent<HTMLInputElement>) => void
}
const defaultValidity = {
type: "invalid",
message: "",
} satisfies ValidationState
export const getValidity = (
e: ChangeEvent<HTMLInputElement>,
): ValidationState =>
e.target.checkValidity()
? { type: "valid" }
: { type: "invalid", message: e.target.validationMessage }
export const useInputValidation = () => {
const [val, set] = useState<ValidationState>(defaultValidity)
const validate = (e: ChangeEvent<HTMLInputElement>) => set(getValidity(e))
return { state: val, validate }
}
export const useInputValidations = <const T extends readonly string[]>(
keys: T,
) => {
const [vals, set] = useState<Record<string, ValidationState>>(
Object.fromEntries(keys.map((key) => [key, defaultValidity])),
)
const result = mapEntries(vals, ([key, val]) => [
key,
{
state: val,
validate: (e: ChangeEvent<HTMLInputElement>) =>
set({ ...vals, [key]: getValidity(e) }),
},
])
return result as { [K in T[number]]: UseInputValidation }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment