React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

react-19troubleshootingproblemssolutions

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.


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 />);