Problem/Solution
30 common React 19 problems developers face, each with a concise fix. Ordered most common first.
1. forwardRef feels redundant after upgrading
Problem: 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} />;
}2. useFormState import broken
Problem: 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);3. Hydration mismatch errors are louder
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.
4. <title> and <meta> duplicating
Problem: react-helmet + React 19 native metadata hoisting produces duplicate tags.
Solution: Remove react-helmet. Render <title>/<meta> directly anywhere — React 19 hoists them.
5. Server Action errors show as generic digest
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" };
return { ok: true };
}6. use(promise) causing infinite suspense
Problem: 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);7. Async components crash the client
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.
8. useOptimistic value flashes back
Problem: 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);
});9. Context provider boilerplate
Problem: <MyContext.Provider value={...}> feels verbose.
Solution: Render the context itself — React 19 accepts <MyContext value={...}>.
10. Ref cleanup missing
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();
}} />11. Form state not resetting after submit
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>12. useActionState initial state crash
Problem: Passing undefined as initial state breaks first render.
Solution: Always pass an explicit default like null or {}.
13. useFormStatus always returns pending: false
Problem: 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.
14. Server Action returning non-serializable data
Problem: Returning Date, Map, or class instances silently breaks.
Solution: Return only JSON-compatible plain objects.
15. Metadata inside Suspense not showing in <head>
Problem: <title> renders late when inside a suspended tree.
Solution: Hoist metadata above the Suspense boundary — layouts are ideal.
16. forwardRef generic components lose TS inference
Problem: Converting a generic forwardRef to ref prop loses type inference.
Solution: Keep forwardRef for that component, or tighten the generic constraint.
17. useTransition not deferring expensive renders
Problem: 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.
18. Server Component importing a client-only library crashes
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.
19. cache() not deduping across requests
Problem: 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.
20. use() in a conditional after hooks errors
Problem: 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.
21. Strict Mode double-invokes Server Actions in dev
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.
22. ref as prop + third-party lib expecting forwardRef
Problem: A lib wraps your component and expects forwardRef semantics.
Solution: Keep forwardRef at the integration boundary — compatibility still works.
23. useDeferredValue initial value flashing
Problem: On first render you want to skip the old value.
Solution: React 19 accepts a second argument: useDeferredValue(value, initialValue).
const deferred = useDeferredValue(query, "");24. Suspense fallback flashing on navigation
Problem: Fallback flickers even for fast transitions.
Solution: Wrap the navigation in startTransition so React shows stale content instead of the fallback.
25. <link rel="stylesheet"> loading out of order
Problem: 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" />26. Preloading resources manually
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" });27. Error boundary not catching Server Action errors
Problem: Server Action error bypasses your <ErrorBoundary>.
Solution: Error boundaries catch render errors, not action rejections. Surface errors via useActionState return value instead.
28. Form action={serverAction} TypeScript error
Problem: TS complains about passing a server function to action.
Solution: Ensure @types/react ≥ 19 and the action signature matches (formData: FormData) => void | Promise<void>.
29. "use server" file leaking client code
Problem: 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.
30. Migration breaks legacy ReactDOM.render
Problem: 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 />);Related
- React Rules — Core React best practices
- Next.js Rules — App Router specific guidance
- API Rules — Server Action and API patterns