Every AI coding assistant was trained on React 18 patterns. When you're building with React 19 + Next.js 15, the AI generates code that compiles perfectly but fails at runtime or behaves unexpectedly.
Here are the 4 most common React 19 traps I've documented, with .mdc rules to prevent each one.
1. useFormStatus() returns { pending: false } forever
The trap: The AI places useFormStatus() in the same component that renders the <form>. This will NEVER work — useFormStatus only reads status from a parent <form>.
// ❌ AI generates this — pending is ALWAYS false
'use client'
export function MyForm() {
const { pending } = useFormStatus()
return (
<form action={createItem}>
<Button disabled={pending}>Submit</Button>
</form>
)
}
// ✅ Extract the button to a child component
function SubmitButton() {
const { pending } = useFormStatus()
return <Button disabled={pending}>{pending ? 'Saving...' : 'Submit'}</Button>
}
export function MyForm() {
return (
<form action={createItem}>
<SubmitButton />
</form>
)
}
2. useFormState is deprecated — AI still imports it
React 19 renamed useFormState to useActionState and moved it from react-dom to react. The AI generates the old import because its training data is 99% React 18.
// ❌ Deprecated — AI generates this
import { useFormState } from 'react-dom'
// ✅ React 19
import { useActionState } from 'react'
const [state, formAction, isPending] = useActionState(serverAction, initialState)
3. forwardRef is no longer needed
In React 19, ref is a regular prop. The AI wraps every component in forwardRef() because that's what it learned from thousands of React 18 examples.
// ❌ Unnecessary boilerplate in React 19
const MyInput = forwardRef<HTMLInputElement, Props>((props, ref) => {
return <input ref={ref} {...props} />
})
// ✅ React 19 — ref is a regular prop
function MyInput({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />
}
4. params are Promises in Next.js 15
This is the #1 runtime crash. In Next.js 15, params and searchParams are Promise objects. The AI generates synchronous destructuring because Next.js 14 used synchronous params.
// ❌ Compiles in dev, crashes in production
export default function Page({ params }: { params: { id: string } }) {
const { id } = params
}
// ✅ Next.js 15 — params are async
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
}
The .mdc solution
Each of these traps has a corresponding .mdc rule file that activates based on file globs. When the AI opens a .tsx file, the rules inject the correct patterns into the context window, overriding the stale training data.
I've documented 29 of these patterns (React 19, Next.js 15, Supabase SSR, Stripe) as open source .mdc rule files:
GitHub: github.com/vibestackdev/vibe-stack
Quick install (5 free rules):
npx vibe-stack-rules init
What React 19 traps have you hit with AI code generation? I'd love to add them to the rule set.
Top comments (0)