Overview
Submit a server action that returns ActionResponse, show success/error with ActionMessageToast, and keep UX consistent via TransitionButton.
'use client'
import * as React from 'react'
import { TransitionButton } from '@/components/TransitionButton'
import { ActionMessageToast } from '@/components/ActionMessageToast'
import { updateUserProfileAction } from '@/actions/updateUserProfileAction'
export function ProfileForm() {
const [isPending, startTransition] = React.useTransition()
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const formData = new FormData(e.currentTarget)
startTransition(async () => {
try {
const res = await updateUserProfileAction(formData) // ActionResponse
if (res?.success) ActionMessageToast.success('Profile updated')
else ActionMessageToast.error(res?.error ?? 'Update failed')
} catch {
ActionMessageToast.error('Unexpected error')
}
})
}
return (
<form onSubmit={onSubmit} className="grid gap-4">
<input name="name" placeholder="Your name" />
<TransitionButton type="submit" isPending={isPending}>
Save changes
</TransitionButton>
</form>
)
}Key conventions
- Server actions return
ActionResponse({ success: boolean; error?: string; data?: T }). - Use
React.useTransition()— notuseState— so the transition blocks concurrent renders while the action is in flight. TransitionButtonacceptsisPendingand renders a spinner automatically.ActionMessageToastwraps the shadcn toast in a single-call API (success,error,info).- Wrap the
startTransitionbody intry/catchto surface network-level failures separately from action errors.