← Back

useActionState for Form Errors

Preserve form input on validation errors, reuse forms for create and edit with .bind().

PublishedMarch 5, 2026122 words
Edit

useActionState

useActionState manages form state across submissions, preserving input after validation errors.

Example: PostForm

From app/dashboard/_components/PostForm.tsx:

'use client'; import { useActionState } from 'react'; export function PostForm({ action, defaultValues, redirectTo }) { const router = useRouter(); const [state, formAction] = useActionState(async (_prev, formData) => { const result = await action(formData); if (result.success) { router.push(redirectTo); return _prev; } return result.formData ?? _prev; }, defaultValues); return ( <form action={formAction}> <input name="title" defaultValue={state.title} /> <SubmitButton>Save</SubmitButton> </form> ); }
'use client'; import { useActionState } from 'react'; export function PostForm({ action, defaultValues, redirectTo }) { const router = useRouter(); const

On error, the form data is returned so fields keep their values.

Reusing for Create and Edit

// Create <PostForm action={createPost} defaultValues={{ title: '' }} /> // Edit - use .bind() to apply the slug <PostForm action
[
state
,
formAction
]
=
useActionState
(
async
(
_prev
,
formData
)
=>
{
const
result
=
await
action
(
formData
)
;
if
(
result
.
success
)
{
router
.
push
(
redirectTo
)
;
return
_prev
;
}
return
result
.
formData
??
_prev
;
}
,
defaultValues
)
;
return
(
<
form
action
=
{
formAction
}
>
<
input
name
=
"
title
"
defaultValue
=
{
state
.
title
}
/>
<
SubmitButton
>
Save
</
SubmitButton
>
</
form
>
)
;
}
=
{
updatePost
.
bind
(
null
,
post
.
slug
)
}
defaultValues
=
{
post
}
/>
// Create <PostForm action={createPost} defaultValues={{ title: '' }} /> // Edit - use .bind() to apply the slug <PostForm action={updatePost.bind(null, post.slug)} defaultValues={post} />