Search across all documentation pages
30 common React 19 problems developers face, each with a concise fix. Ordered most common first.
forwardRef feels redundant after upgradingProblem: You have dozens of forwardRef components that now feel like boilerplate.
Solution: Accept ref as a regular prop.
interface InputProps extends React.ComponentPropsWithoutRef<"input"> {
ref?: React.Ref<HTMLInputElement>;
}
export function Input({ ref, ...props }: InputProps) {
return <input ref={ref} {...props} />;
}useFormState import brokenProblem: useFormState from react-dom warns or fails.
Solution: Import useActionState from react — same signature plus isPending.
import { useActionState } from "react";
const [state, action, isPending] = useActionState(submit, null);Problem: Code that worked in React 18 now throws hydration errors.
Solution: Move window, Date.now(), Math.random() into useEffect or a client-only child.
<title> and <meta> duplicatingProblem: react-helmet + React 19 native metadata hoisting produces duplicate tags.
Solution: Remove react-helmet. Render <title>/<meta> directly anywhere — React 19 hoists them.
Problem: Thrown errors inside Server Actions surface as Server Error with no stack in prod.
Solution: Return errors as values, not exceptions.
"use server";
export async function save(_: State, fd: FormData) {
const title = fd.get("title");
if (!title) return { error: "Title required" };
use(promise) causing infinite suspenseProblem: Component suspends forever after adding use().
Solution: The promise must be stable across renders — create it in a parent or memoize.
const userPromise = useMemo(() => fetchUser(id), [id]);
const user = use(userPromise);Problem: Marking a Client Component async throws "async/await is not yet supported."
Solution: Async components are server-only. Fetch on server, pass as prop, or use use() on client.
useOptimistic value flashes backProblem: Optimistic UI reverts before the server responds.
Solution: Wrap the mutation in startTransition or call from a Server Action.
startTransition(async () => {
addOptimistic(item);
await saveItem(item);
});Problem: <MyContext.Provider value={...}> feels verbose.
Solution: Render the context itself — React 19 accepts <MyContext value={...}>.
Problem: No teardown hook when a DOM ref unmounts.
Solution: Return a cleanup function from the ref callback.
<div ref={(node) => {
const ob = new ResizeObserver(() => {});
if (node) ob.observe(node);
return () => ob.disconnect();
}} />Problem: Form fields keep their values after a successful Server Action.
Solution: Reset via form.reset() in a requestFormReset inside the action, or use an uncontrolled key prop.
<form key={submitCount} action={action}>...</form>useActionState initial state crashProblem: Passing undefined as initial state breaks first render.
Solution: Always pass an explicit default like null or {}.
useFormStatus always returns pending: falseProblem: useFormStatus() called in the parent of the <form> never updates.
Solution: It only reads status from an ancestor <form>. Call it from a child rendered inside the form.
Problem: Returning Date, Map, or class instances silently breaks.
Solution: Return only JSON-compatible plain objects.
<head>Problem: <title> renders late when inside a suspended tree.
Solution: Hoist metadata above the Suspense boundary — layouts are ideal.
forwardRef generic components lose TS inferenceProblem: Converting a generic forwardRef to ref prop loses type inference.
Solution: Keep forwardRef for that component, or tighten the generic constraint.
useTransition not deferring expensive rendersProblem: UI still janks even after wrapping in startTransition.
Solution: Ensure the expensive work reads from state updated inside the transition, not a separate synchronous setState.
Problem: Importing a browser-only lib into a Server Component throws window is not defined.
Solution: Add "use client" at the top of the file, or dynamically import with next/dynamic + ssr: false.
cache() not deduping across requestsProblem: cache() from React seems to cache per-request but not across.
Solution: That's by design — cache() is per-request memoization. For cross-request, use unstable_cache (Next.js) or an external cache.
use() in a conditional after hooks errorsProblem: Calling use() inside an if after another hook breaks the rules of hooks.
Solution: use() can be conditional, but it still counts as a hook call — keep it before any early returns.
Problem: Server Action runs twice in development.
Solution: That's Strict Mode's intentional double-invoke. Make actions idempotent or disable Strict Mode only as a last resort.
ref as prop + third-party lib expecting forwardRefProblem: A lib wraps your component and expects forwardRef semantics.
Solution: Keep forwardRef at the integration boundary — compatibility still works.
useDeferredValue initial value flashingProblem: On first render you want to skip the old value.
Solution: React 19 accepts a second argument: useDeferredValue(value, initialValue).
const deferred = useDeferredValue(query, "");Problem: Fallback flickers even for fast transitions.
Solution: Wrap the navigation in startTransition so React shows stale content instead of the fallback.
<link rel="stylesheet"> loading out of orderProblem: Multiple components render the same stylesheet and order is unstable.
Solution: Use the precedence prop — React 19 de-dupes and orders them.
<link rel="stylesheet" href="/a.css" precedence="default" />Problem: You inject <link rel="preload"> by hand and it runs too late.
Solution: Use React 19's resource APIs: preload, preinit, prefetchDNS from react-dom.
import { preload } from "react-dom";
preload("/hero.jpg", { as: "image" });Problem: Server Action error bypasses your <ErrorBoundary>.
Solution: Error boundaries catch render errors, not action rejections. Surface errors via useActionState return value instead.
action={serverAction} TypeScript errorProblem: TS complains about passing a server function to action.
Solution: Ensure @types/react ≥ 19 and the action signature matches (formData: FormData) => void | Promise<void>.
"use server" file leaking client codeProblem: Importing shared utilities into a "use server" file bundles client-only code server-side.
Solution: Keep "use server" files focused on actions — extract shared logic to a neutral lib/ file with no React imports.
ReactDOM.renderProblem: Old bootstrap code using ReactDOM.render no longer compiles.
Solution: Switch to createRoot (required since 18, enforced in 19).
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")!).render(<App />);