🥴
conformとserver actionの汎用のhook
概要
conformとserver actionの汎用のhookを実装するだけ
参考
実装
//
import type { FormOptions, SubmissionResult } from "@conform-to/dom"
import { useForm } from "@conform-to/react"
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
import { useFormState, useFormStatus } from "react-dom"
import type { z } from "zod"
export type ConformStateType = SubmissionResult<string[]> | undefined
export type ConformServerAction = (
previous: ConformStateType,
formData: FormData
) => Promise<ConformStateType>
export const useConformAction = (
serverAction: ConformServerAction,
{
schema,
...options
/** formIdがomitしなきゃ必須になっちまう */
}: Omit<FormOptions<z.output<z.ZodType>>, "formId"> & {
schema: z.ZodType
}
) => {
const [lastResult, action] = useFormState(serverAction, undefined)
const [form, fields] = useForm<z.output<z.ZodType>>({
lastResult,
onValidate({ formData }) {
const parsed = parseWithZod(formData, { schema })
return parsed
},
constraint: getZodConstraint(schema),
...options,
})
return [form, fields, action] as const
}
// component
export const Login = () => {
const [form, fields, action] = useConformAction(
async (prev: ConformStateType, action: FormData) => {
const result = await loginAction(prev, action)
return result
},
{
schema: loginSchema,
defaultValue: {
username: "",
password: "",
},
}
)
return (
<form action={action} {...getFormProps(form)}>
<AnyField error={fields.username.errors}>
<Input
placeholder="username"
{...getInputProps(fields.username, { type: "text" })}
/>
</AnyField>
<AnyField error={fields.password.errors}>
<Input
placeholder="password"
{...getInputProps(fields.password, { type: "password" })}
/>
</AnyField>
<ActionButton type="submit">
送信
</ActionButton>
</form>
)
}
// ActionButton
import { Button, ButtonProps } from "./Button"
export const ActionButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ disabled, ...props }, ref) => {
const status = useFormStatus()
return <Button disabled={disabled || status.pending} ref={ref} {...props} />
}
)
ActionButton.displayName = "ActionButton"
-
useConformAction
はuseFormState
とuseForm
を包み込んでいます。useActionState
に完全に変更された時移行しやすいようにuseFormState
もいれるようにしてます。 - componentから
useConformAction
を呼び出して使うだけです。 -
ActionButton
はuseFormStatus
がformの中でレンダリングされていないと使えないので、Button
をラップして使えるようにしています。
以上。
Discussion