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>
  );
}