React 19 Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
@types/react and @types/react-dom must be upgraded in the same install to the v19 line; mismatched versions produce confusing type errors (e.g. ref missing from ComponentProps) that look like React bugs.<Context.Provider value={…}> is deprecated - render <ThemeContext value={theme}>…</ThemeContext> directly; the old .Provider form still works but logs a warning and is destined for removal.await directly in the body - Client Components cannot be async, cannot use useState/useEffect, and have no access to window, document, or localStorage.children or named JSX props from a Server Component parent.Date, Map, Set, FormData, typed arrays, unawaited Promises, and Server Actions - but never regular functions, class instances, or DOM nodes."use server" converts individual async functions into RPC endpoints, so avoid putting it at the top of a component file expecting all exports to become actions - that breaks the component exports instead.return { success: true, data } or return { success: false, error: "Invalid" } - and reserve throw for genuinely unexpected failures caught by error.tsx."use server" actions are serialized as encrypted hidden form fields on every render, so extract heavy closures into top-level action files when the captured state gets large.useFormStatus() only reflects the nearest ancestor <form>, so calling it in the same component that renders the form always returns pending: false - move the status-reading button into a child of the form.return { ...prevState, error: "Invalid email" } - and remember to import it from "react", not "react-dom".use() causes an infinite Suspense loop; hoist promise creation to a parent Server Component, a stable cache, or a prop so the reference is stable across renders.use(promise) requires a Suspense ancestor to show the loading state and an error boundary to catch rejections - without both, the app either throws or silently hangs on pending promises.use() is legal inside if, loops, and early returns - if (shouldLoad) { const data = use(promise) } - take advantage of this for conditional context reads and guarded promise unwraps instead of forcing old useContext/useEffect shapes.addOptimistic silently does nothing unless invoked inside a form action, Server Action handler, or an explicit startTransition; wrap event-handler calls: startTransition(() => addOptimistic(newItem)) so the optimistic update actually applies.updateFn passed to useOptimistic must be pure and produce a new value - mutating state or referencing side-effectful scope breaks the automatic rollback on failure.useOptimistic with useActionState so errors still render to the user after the revert.ref as an ordinary prop, so {...props} accidentally forwards ref alongside everything else - destructure it explicitly: ({ ref, ...props }) => <input ref={ref} {...props} />, especially when the component is a passthrough.useEffect); returning anything else (non-function, non-undefined) triggers a dev warning, so either return a cleanup or return nothing at all.<link rel="stylesheet" precedence="…"> opts into React's dedup, ordering, and Suspense-until-loaded - without precedence, the stylesheet is unmanaged and you can hit FOUC or duplicate sheets.<title> to <head> but only the last-rendered one wins, so scattering titles across components produces order-dependent results; pick a single <title> site (or use the framework's metadata API) and stick with it.preload when you only want the bytes in cache and preinit when you also want scripts to run or stylesheets to apply; preinit runs scripts immediately and will fail if they depend on DOM that has not rendered yet.crossOrigin: "anonymous" on font preloads even for same-origin requests - preload("/font.woff2", { as: "font", crossOrigin: "anonymous" }) - without it the browser downloads the font twice and the preload hint is wasted.compilationMode: "annotation" and the "use memo" directive for a gradual rollout, and use "use no memo" to opt specific functions out.Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥