Skip to content

Instantly share code, notes, and snippets.

@zaknesler
Last active January 19, 2023 14:37
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 zaknesler/4e22aa1c81d1e6af8cb4be9cbfe440e8 to your computer and use it in GitHub Desktop.
Save zaknesler/4e22aa1c81d1e6af8cb4be9cbfe440e8 to your computer and use it in GitHub Desktop.
Mutable form helper for Next.js + tRPC + zod
import {
BaseSyntheticEvent,
ChangeEventHandler,
useEffect,
useState,
} from 'react'
import { ErrorType } from '@/server/trpc/trpc'
import { RouterInputs, RouterOutputs, trpc } from '@/utils/trpc'
export type Fields = Pick<
JSX.IntrinsicElements['input'],
'name' | 'value' | 'disabled'
> & {
onChange?: ChangeEventHandler | undefined
errors?: string[] | null
}
const useMutableForm = <
K extends keyof RouterInputs,
H extends keyof RouterInputs[K],
>(
key: K,
handler: H,
initialData: RouterInputs[K][H],
) => {
const mutation = (trpc as any)[key][handler].useMutation()
const [data, setData] = useState<RouterInputs[K][H]>(initialData)
const errors = mutation.error?.data?.fields as ErrorType<RouterInputs[K][H]>
const loading = mutation.isLoading as boolean
const reset = () => {
setData(initialData)
mutation.reset()
}
const hasDataChanged = JSON.stringify(initialData) !== JSON.stringify(data)
const mutate = async () => mutation.mutateAsync(data) as RouterOutputs[K][H]
const fields = (name: keyof RouterInputs[K][H]): Fields => ({
name: String(name),
value: String((data as any)[name]),
errors: errors?.[name],
onChange: changeHandler as any,
disabled: loading,
})
const changeHandler = (e: BaseSyntheticEvent) => {
setData({
...(data as any),
[e.target.name]: ['checkbox', 'radio'].includes(e.target.type)
? e.target.checked
: ['range', 'number'].includes(e.target.type)
? Number(e.target.value)
: e.target.value,
})
}
useEffect(() => {
if (!window || !document || !errors) return
const error = Object.keys(errors)[0]
const el = document.querySelector(`[name="${error}"]`)
el && (el as any).focus()
}, [errors])
return {
mutate,
form: data,
errors,
loading,
fields,
reset,
setData,
changeHandler,
hasDataChanged,
}
}
export default useMutableForm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment