The Death of Client Form Boilerplate
For a decade, handling a simple form in React required `useState`, `handleSubmit`, loading spinners, error states, and external libraries like Redux Toolkit or React Hook Form.
React 19 introduces native primitives that handle asynchronous state transitions with zero client-side boilerplate.
'use client';
import { useActionState, useOptimistic } from 'react';
import { updateProjectName } from '@/actions/project';
export function ProjectRenameForm({ currentName, projectId }: { currentName: string; projectId: string }) {
const [state, formAction, isPending] = useActionState(updateProjectName, null);
const [optimisticName, setOptimisticName] = useOptimistic(
currentName,
(old, newName: string) => newName
);
return (
<form action={async (formData) => {
const newName = formData.get('name') as string;
setOptimisticName(newName);
await formAction(formData);
}}>
<input defaultValue={optimisticName} name="name" className="border px-4 py-2" />
<button disabled={isPending}>{isPending ? 'Saving...' : 'Update'}</button>
</form>
);
}
