🎯

react-19

🎯Skill

from noklip-io/agent-skills

VibeIndex|
What it does

Guides React developers through React 19's key patterns, breaking changes, and new paradigms for modern component and application development.

πŸ“¦

Part of

noklip-io/agent-skills(7 items)

react-19

Installation

npxRun with npx
npx codemod@latest react/19/migration-recipe
πŸ“– Extracted from docs: noklip-io/agent-skills
23Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

>

Overview

# React 19 - Key Changes

This skill focuses on what changed in React 19. Not a complete React reference.

Coming from React 16/17?

If upgrading from pre-18 versions, these changes accumulated and are now mandatory:

| Change | Introduced | React 19 Status |

|--------|------------|-----------------|

| createRoot / hydrateRoot | React 18 | Required (ReactDOM.render removed) |

| Concurrent rendering | React 18 | Foundation for all R19 features |

| Automatic batching | React 18 | Default behavior |

| useId, useSyncExternalStore | React 18 | Stable, commonly used |

| Hooks (no classes for new code) | React 16.8 | Only path for new features |

| createContext (not legacy) | React 16.3 | Required (legacy Context removed) |

| Error Boundaries | React 16 | Now with better error callbacks |

Migration path: Upgrade to React 18.3 first (shows deprecation warnings), then to 19.

The React 19 Mindset

React 19 represents fundamental shifts in how to think about React:

| Old Thinking | New Thinking |

|--------------|--------------|

| Client-side by default | Server-first (RSC default) |

| Manual memoization | Compiler handles it |

| useEffect for data | async Server Components |

| useState for forms | Form Actions |

| Loading state booleans | Suspense boundaries |

| Optimize everything | Write correct code, compiler optimizes |

See [references/paradigm-shifts.md](./references/paradigm-shifts.md) for the mental model changes.

See [references/anti-patterns.md](./references/anti-patterns.md) for what to stop doing.

Quick Reference: What's New

| Feature | React 18 | React 19+ |

|---------|----------|-----------|

| Memoization | Manual (useMemo, useCallback, memo) | React Compiler (automatic) or manual |

| Forward refs | forwardRef() wrapper | ref as regular prop |

| Context provider | | |

| Form state | Custom with useState | useActionState hook |

| Optimistic updates | Manual state management | useOptimistic hook |

| Read promises | Not possible in render | use() hook |

| Conditional context | Not possible | use(Context) after conditionals |

| Form pending state | Manual tracking | useFormStatus hook |

| Ref cleanup | Pass null on unmount | Return cleanup function |

| Document metadata | react-helmet or manual | Native </code>, <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-800"><meta></code>, <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-800"><link></code> |</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>| Hide/show UI with state | Unmount/remount (state lost) | <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-800"><Activity></code> component (19.2+) |</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>| Non-reactive Effect logic | Add to deps or suppress lint | <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-800">useEffectEvent</code> hook (19.2+) |</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>| Custom Elements | Partial support | Full support (props as properties) |</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>| Hydration errors | Multiple vague errors | Single error with diff |</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">React Compiler & Memoization</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>With React Compiler enabled, manual memoization is <strong>optional, not forbidden</strong>:</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React Compiler handles this automatically</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Component({ items }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const filtered = items.filter(x => x.active);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const handleClick = (id) => console.log(id);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return <List items={sorted} onClick={handleClick} />;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Manual memoization still works as escape hatch for fine-grained control</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>const filtered = useMemo(() => expensiveOperation(items), [items]);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>const handleClick = useCallback((id) => onClick(id), [onClick]);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><strong>When to use manual memoization with React Compiler:</strong></span></p><ul class="mb-4 ml-6 space-y-1 list-disc"><li class="text-zinc-600 dark:text-zinc-400"><span>Effect dependencies that need stable references</span></li><li class="text-zinc-600 dark:text-zinc-400"><span>Sharing expensive calculations across components (compiler doesn't share)</span></li><li class="text-zinc-600 dark:text-zinc-400"><span>Explicit control over when re-computation happens</span></li></ul><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>See [references/react-compiler.md](./references/react-compiler.md) for details.</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">ref as Prop (forwardRef Deprecated)</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React 19: ref is just a prop</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Input({ placeholder, ref }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return <input placeholder={placeholder} ref={ref} />;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Usage - no change</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>const inputRef = useRef(null);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><Input ref={inputRef} placeholder="Enter text" /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// forwardRef still works but will be deprecated</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Codemod: npx codemod@latest react/19/replace-forward-ref</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">Ref Cleanup Functions</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React 19: Return cleanup function from ref callback</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><input</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> ref={(node) => {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> // Setup</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> node?.focus();</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> // Return cleanup (called on unmount or ref change)</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return () => {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> console.log('Cleanup');</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> };</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> }}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>/></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React 18: Received null on unmount (still works, but cleanup preferred)</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><input ref={(node) => {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> if (node) { /<em> setup </em>/ }</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> else { /<em> cleanup </em>/ }</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}} /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">Context as Provider</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>const ThemeContext = createContext('light');</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React 19: Use Context directly</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function App({ children }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <ThemeContext value="dark"></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {children}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </ThemeContext></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// React 18: Required .Provider (still works, will be deprecated)</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><ThemeContext.Provider value="dark"></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {children}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span></ThemeContext.Provider></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">New Hooks</h3><div><h4 class="mb-2 mt-4 font-semibold text-zinc-800 dark:text-zinc-200">useActionState</h4><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>import { useActionState } from 'react';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Form() {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const [error, submitAction, isPending] = useActionState(</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> async (prevState, formData) => {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const result = await saveData(formData.get('name'));</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> if (result.error) return result.error;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> redirect('/success');</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return null;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> },</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> null // initial state</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <form action={submitAction}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <input name="name" disabled={isPending} /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <button disabled={isPending}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {isPending ? 'Saving...' : 'Save'}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </button></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {error && <p className="error">{error}</p>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p><h4 class="mb-2 mt-4 font-semibold text-zinc-800 dark:text-zinc-200">useOptimistic</h4><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>import { useOptimistic } from 'react';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Messages({ messages, sendMessage }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const [optimisticMessages, addOptimistic] = useOptimistic(</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> messages,</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> (state, newMessage) => [...state, { ...newMessage, sending: true }]</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> async function handleSubmit(formData) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const message = { text: formData.get('text'), id: Date.now() };</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> addOptimistic(message); // Show immediately</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> await sendMessage(message); // Reverts on error</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> }</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <form action={handleSubmit}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {optimisticMessages.map(m => (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <div key={m.id} style={{ opacity: m.sending ? 0.5 : 1 }}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {m.text}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </div></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> ))}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <input name="text" /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p><h4 class="mb-2 mt-4 font-semibold text-zinc-800 dark:text-zinc-200">use() Hook</h4><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>import { use, Suspense } from 'react';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Read promises (suspends until resolved)</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Comments({ commentsPromise }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const comments = use(commentsPromise);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return comments.map(c => <p key={c.id}>{c.text}</p>);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Usage with Suspense</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><Suspense fallback={<Spinner />}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <Comments commentsPromise={fetchComments()} /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span></Suspense></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Conditional context reading (not possible with useContext!)</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Theme({ showTheme }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> if (!showTheme) return <div>Plain</div>;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const theme = use(ThemeContext); // Can be called conditionally!</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return <div style={{ color: theme.primary }}>Themed</div>;</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p><h4 class="mb-2 mt-4 font-semibold text-zinc-800 dark:text-zinc-200">useFormStatus (react-dom)</h4><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>import { useFormStatus } from 'react-dom';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Must be used inside a <form> - reads parent form status</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function SubmitButton() {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> const { pending, data, method, action } = useFormStatus();</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <button disabled={pending}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> {pending ? 'Submitting...' : 'Submit'}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </button></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function Form() {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <form action={serverAction}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <input name="email" /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <SubmitButton /> {/<em> Reads form status via context </em>/}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> </form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> );</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>See [references/new-hooks.md](./references/new-hooks.md) for complete API details.</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">Form Actions</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Pass function directly to form action</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><form action={async (formData) => {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> 'use server';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> await saveToDatabase(formData);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>}}></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <input name="email" type="email" /></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <button type="submit">Subscribe</button></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span></form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Button-level actions</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span><form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <button formAction={saveAction}>Save</button></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <button formAction={deleteAction}>Delete</button></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span></form></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Manual form reset</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>import { requestFormReset } from 'react-dom';</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>requestFormReset(formElement);</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```</span></p></div></div><div class="mt-6 border-t border-zinc-100 pt-6 dark:border-zinc-800"><h3 class="mb-3 text-lg font-semibold text-zinc-900 dark:text-white">Document Metadata</h3><div><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>```tsx</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>// Automatically hoisted to <head> - works in any component</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span>function BlogPost({ post }) {</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> return (</span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <article></span></p><p class="mb-3 text-zinc-600 dark:text-zinc-400"><span> <title>{post.title}

{post.title}

{post.content}

);

}

```

Resource Preloading

```tsx

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';

function App() {

// DNS prefetch

prefetchDNS('https://api.example.com');

// Establish connection early

preconnect('https://fonts.googleapis.com');

// Preload resources

preload('https://example.com/font.woff2', { as: 'font' });

preload('/hero.jpg', { as: 'image' });

// Load and execute script eagerly

preinit('https://example.com/analytics.js', { as: 'script' });

return

...
;

}

```

Stylesheet Support

```tsx

// precedence controls insertion order and deduplication

function Component() {

return (

<>

Content

);

}

// React ensures stylesheets load before Suspense boundary reveals

}>

```

Custom Elements Support

React 19 adds full support for Custom Elements (Web Components).

```tsx

// Props matching element properties are assigned as properties

// Others are assigned as attributes

stringAttr="hello" // Attribute (string)

complexProp={{ foo: 'bar' }} // Property (object)

onCustomEvent={handleEvent} // Property (function)

/>

```

Client-side: React checks if a property exists on the element instance. If yes, assigns as property; otherwise, as attribute.

Server-side (SSR): Primitive types (string, number) render as attributes. Objects, functions, symbols are omitted from HTML.

```tsx

// Define custom element

class MyElement extends HTMLElement {

constructor() {

super();

this.data = undefined; // React will assign to this property

}

connectedCallback() {

this.textContent = JSON.stringify(this.data);

}

}

customElements.define('my-element', MyElement);

// Use in React

```

Hydration Improvements

Better Error Messages

React 19 shows a single error with a diff instead of multiple vague errors:

```

Uncaught Error: Hydration failed because the server rendered HTML

didn't match the client.

+ Client

  • Server

```

Third-Party Script Compatibility

React 19 gracefully handles elements inserted by browser extensions or third-party scripts:

  • Unexpected tags in and are skipped (no mismatch errors)
  • Stylesheets from extensions are preserved during re-renders
  • No need to add suppressHydrationWarning for extension-injected content

Removed APIs (Breaking)

| Removed | Migration |

|---------|-----------|

| ReactDOM.render() | createRoot().render() |

| ReactDOM.hydrate() | hydrateRoot() |

| unmountComponentAtNode() | root.unmount() |

| ReactDOM.findDOMNode() | Use refs |

| propTypes | TypeScript or remove |

| defaultProps (functions) | ES6 default parameters |

| String refs | Callback refs or useRef |

| Legacy Context | createContext |

| React.createFactory | JSX |

| react-dom/test-utils | act from 'react' |

See [references/deprecations.md](./references/deprecations.md) for migration guides.

TypeScript Changes

```tsx

// useRef requires argument

const ref = useRef(null); // Required

const ref = useRef(); // Error in React 19

// Ref callbacks must not return values (except cleanup)

{ instance = node; }} /> // Correct

(instance = node)} /> // Error - implicit return

// ReactElement props are now unknown (not any)

type Props = ReactElement['props']; // unknown in R19, any in R18

// JSX namespace - import explicitly

import type { JSX } from 'react';

```

See [references/typescript-changes.md](./references/typescript-changes.md) for codemods.

Migration Codemods

```bash

# Run all React 19 codemods

npx codemod@latest react/19/migration-recipe

# Individual codemods

npx codemod@latest react/19/replace-reactdom-render

npx codemod@latest react/19/replace-string-ref

npx codemod@latest react/19/replace-act-import

npx codemod@latest react/19/replace-use-form-state

npx codemod@latest react/prop-types-typescript

# TypeScript types

npx types-react-codemod@latest preset-19 ./src

```

Imports (Best Practice)

```tsx

// Named imports (recommended)

import { useState, useEffect, useRef, use } from 'react';

import { createRoot } from 'react-dom/client';

import { useFormStatus } from 'react-dom';

// Default import still works but named preferred

import React from 'react'; // Works but not recommended

```

Error Handling Changes

```tsx

// React 19 error handling options

const root = createRoot(container, {

onUncaughtError: (error, errorInfo) => {

// Errors not caught by Error Boundary

console.error('Uncaught:', error, errorInfo.componentStack);

},

onCaughtError: (error, errorInfo) => {

// Errors caught by Error Boundary

reportToAnalytics(error);

},

onRecoverableError: (error, errorInfo) => {

// Errors React recovered from automatically

console.warn('Recovered:', error);

}

});

```

See [references/suspense-streaming.md](./references/suspense-streaming.md) for Suspense patterns and error boundaries.

React 19.2+ Features

Activity Component (19.2)

Hide/show UI while preserving state (like background tabs):

```tsx

import { Activity } from 'react';

// State preserved when hidden, Effects cleaned up

```

useEffectEvent Hook (19.2)

Extract non-reactive logic from Effects without adding dependencies:

```tsx

import { useEffect, useEffectEvent } from 'react';

function Chat({ roomId, theme }) {

// Reads theme without making it a dependency

const onConnected = useEffectEvent(() => {

showNotification(Connected!, theme);

});

useEffect(() => {

const conn = connect(roomId);

conn.on('connected', onConnected);

return () => conn.disconnect();

}, [roomId]); // theme NOT in deps - won't reconnect on theme change

}

```

See [references/react-19-2-features.md](./references/react-19-2-features.md) for complete 19.1+ and 19.2 features.

Reference Documentation

| Document | Content |

|----------|---------|

| [paradigm-shifts.md](./references/paradigm-shifts.md) | Mental model changes - how to think in React 19 |

| [anti-patterns.md](./references/anti-patterns.md) | What to stop doing - outdated patterns |

| [react-19-2-features.md](./references/react-19-2-features.md) | React 19.1+ and 19.2 features (Activity, useEffectEvent) |

| [new-hooks.md](./references/new-hooks.md) | Complete API for 19.0 hooks |

| [server-components.md](./references/server-components.md) | RSC, Server Actions, directives |

| [suspense-streaming.md](./references/suspense-streaming.md) | Suspense, streaming, error handling |

| [react-compiler.md](./references/react-compiler.md) | Automatic memoization details |

| [deprecations.md](./references/deprecations.md) | Removed APIs with migration guides |

| [typescript-changes.md](./references/typescript-changes.md) | Type changes and codemods |