Created
October 21, 2023 18:03
-
-
Save haggen/f34c3bbfb8428f693bc01d77bd62c980 to your computer and use it in GitHub Desktop.
Draft of zod validated form state hook.
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
import { ChangeEvent, createContext, useMemo, useReducer } from "react"; | |
import { AnyZodObject, ZodError, z } from "zod"; | |
import { mapValues } from "~/lib/map"; | |
type FieldState = { | |
name: string; | |
value: string; | |
error: undefined | ZodError; | |
touched: boolean; | |
}; | |
type FormState<T extends Record<string, unknown>> = { | |
[K in keyof T]: FieldState; | |
}; | |
type FormPatch = { | |
name: string; | |
value: string; | |
}; | |
type ContextValue = { | |
fields: Record<string, FieldState & { change(value: string): void }>; | |
parse(): any; | |
}; | |
export const Context = createContext({} as ContextValue); | |
export function createState<T extends AnyZodObject>(schema: T) { | |
return mapValues(schema.shape, (_, name) => ({ | |
name, | |
value: "", | |
error: undefined, | |
touched: false, | |
})) as FormState<z.TypeOf<T>>; | |
} | |
export function getReducer<T extends AnyZodObject>(schema: T) { | |
return (state: FormState<z.TypeOf<T>>, { name, value }: FormPatch) => { | |
if (!name) { | |
return state; | |
} | |
try { | |
state[name].value = schema.shape[name].parse(value); | |
state[name].error = undefined; | |
} catch (err) { | |
if (err instanceof ZodError) { | |
state[name].error = err; | |
} else { | |
throw err; | |
} | |
} finally { | |
state[name].touched = true; | |
} | |
return { ...state } as FormState<z.TypeOf<T>>; | |
}; | |
} | |
export function useForm<T extends AnyZodObject>(schema: T) { | |
const [state, update] = useReducer(getReducer(schema), createState(schema)); | |
console.table(state); | |
const parse = () => { | |
return schema.parse( | |
mapValues(state, (state) => state.value), | |
) as z.TypeOf<T>; | |
}; | |
return { | |
fields: useMemo( | |
() => | |
mapValues(state, (state, name) => ({ | |
...state, | |
change(value: string) { | |
update({ name: name as string, value }); | |
}, | |
})), | |
[state], | |
), | |
parse, | |
} as const; | |
} | |
export function getErrorMessage(ctx: ContextValue, name: string) { | |
return ctx.fields[name].error?.issues[0].message; | |
} | |
export function getInputProps(ctx: ContextValue, name: string) { | |
return { | |
onChange(event: ChangeEvent<HTMLInputElement>) { | |
ctx.fields[name].change(event.target.value); | |
}, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment