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>`.
```tsx
// ❌ 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.
```tsx
// ❌ 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.
```tsx
// ❌ 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.
```tsx
// ❌ 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](https://github.com/vibestackdev/vibe-stack)
**Quick install (5 free rules):**
```bash
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.*