TL;DR: Most results for react prompts ai are copy-paste starter templates. The real fix is naming the defaults AI tools still get wrong after React 19: forwardRef instead of the ref prop, manual useMemo/useCallback instead of the now-stable Compiler, and useEffect for values you could calculate during render. Say your version and those three mostly disappear.
What makes React prompts different from other coding prompts right now?
React's own guidance moved more in the last two years than in the five before it. The React Compiler went from an idea to a stable, shipped tool. Function components gained a plain ref prop. A new class of hook, useActionState, useOptimistic, and use, replaced patterns people used to hand-roll with useEffect and a pile of boolean state. None of that is exotic; all of it is documented on react.dev.
The problem is that a general-purpose coding model was trained mostly on code written before these changes existed, and code before them is still the overwhelming majority of React on the internet. Ask for a React component with a loading state and you'll usually get a correct answer for 2022 and a dated one for today. This isn't the model being wrong about JSX or syntax. It's the model defaulting to the most common pattern in its training data, which is no longer the current pattern.
That's a version problem, and version problems have a version fix: state your React major explicitly, name the specific defaults you don't want, and give the model the one or two idioms your codebase actually uses instead. The rest of this post is what to put in that prompt, verified against React's current documentation rather than the version most tutorials still assume.
Why does AI-generated code still reach for forwardRef instead of the ref prop?
Because forwardRef was the only supported way to expose a DOM node's ref from a function component for years, and that's most of what any model has seen. React's own React 19 release notes are direct about the replacement: "Starting in React 19, you can now access ref as a prop for function components" (React blog, 5 December 2024, accessed 4 September 2026).
The old pattern:
const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />;
});
The current one:
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
Same behavior, no wrapper. React's team is explicit that this isn't a stylistic option they're leaving in place alongside the old one indefinitely: "New function components will no longer need forwardRef" and, further down the same page, that they intend to deprecate and eventually remove it. That's the sentence to paste into your prompt almost verbatim: target React 19, expose refs as a normal prop, don't wrap new components in forwardRef. Existing forwardRef components you already have don't need to be rewritten on sight, but new code a model writes for you shouldn't be adding more of them.
Why do AI-written data-fetching effects still break under real usage?
Because the naive version, calling the API in a useEffect and setting state with the result, has a bug that only shows up under real network variance, and a model simply asked to fetch data in an effect will often produce exactly the naive version. Type a search box fast enough and an earlier, slower request can resolve after a faster later one, silently overwriting the correct result with a stale one. This is a genuine race condition, not a hypothetical.
React's own guide on this is unambiguous about the fix (react.dev, You Might Not Need an Effect, accessed 4 September 2026): add a cleanup function that flags stale results so they get ignored when they land late.
useEffect(() => {
let ignore = false;
fetchResults(query).then((result) => {
if (!ignore) setResults(result);
});
return () => {
ignore = true;
};
}, [query]);
Skip that ignore flag and a search box, a filtered list, or an autocomplete built from a bare fetch-in-effect will occasionally show the wrong result under nothing more exotic than a slow connection. If your prompt asks for data fetching inside a component, add one line: include cleanup to guard against out-of-order responses. Better yet, if your stack ships a data-fetching layer (a router loader, a server component, a query library), ask the model to use that instead. React's own guidance says as much itself: modern frameworks "provide more efficient built-in data fetching mechanisms than writing Effects directly in your components" (react.dev, You Might Not Need an Effect, accessed 4 September 2026).
What's actually state, and what should just be calculated?
The test is simple, and most AI-generated components fail it in the same direction: if a value can be derived from props or state you already have, it isn't state. React's own guidance is direct about this: "something can be calculated from the existing props or state" shouldn't live in state at all, and you should "calculate it during rendering" instead (react.dev, You Might Not Need an Effect, accessed 4 September 2026). That's a calculation that belongs inline in the render, not a second copy tracked with useState and synced with an Effect.
A model asked to build a searchable list will often produce a filteredItems state variable, a useEffect that recalculates it whenever the query changes, and a subtle new place for that Effect to get out of sync with the input. The correct version has no Effect at all:
function SearchableList({ items, query }) {
// No state, no Effect — just a value computed during render
const filteredItems = items.filter((item) => item.name.includes(query));
return <List items={filteredItems} />;
}
Here's the decision your prompt should encode, as a quick reference for the cases that come up most:
| What you're building | Does it belong in useState? |
|---|---|
| A filtered, sorted, or transformed view of state you already hold | No — calculate it during render (wrap in useMemo only if it's measurably slow) |
| The raw value of a controlled input as the user types | Yes |
| A value that only depends on the current props | No — calculate it during render |
| The result of an API call | Yes — there's no local source to derive it from |
| Something that should fully reset when an identifying prop changes | No — give the component a key instead of an Effect that resets state |
The pattern to name in a prompt is short: don't put anything in state that you could compute from state you already have; compute it in the render body instead. That single instruction removes a large share of the unnecessary Effects a model otherwise adds by default, and it matches our post on prompting for frontend components, which covers the sibling problem of components re-rendering because of the same kind of avoidable state.
Does the stable React Compiler mean you should stop asking for useMemo and useCallback?
Mostly, yes, but the honest answer is stop asking for them by default, not never use them again. React Compiler reached its first stable release on 7 October 2025, and React's own announcement doesn't hedge about production-readiness: "The compiler has been battle tested on major apps at Meta and is fully production-ready" (React blog, 7 October 2025, accessed 4 September 2026). The same post reports real numbers from an app that shipped it: "We've seen initial loads and cross-page navigations improve by up to 12%, while certain interactions are more than 2.5× faster."
The compiler's own introduction page states the change in one sentence: "React Compiler is now stable and has been tested extensively in production" (react.dev, React Compiler introduction, accessed 4 September 2026). Practically, that changes what a default React prompt should ask for. The useMemo reference page now carries this note direct from the source: the compiler "automatically memoizes values and functions, reducing the need for manual useMemo calls." The same page still holds the older discipline for the cases the compiler doesn't cover: "You should only rely on useMemo as a performance optimization." Neither statement contradicts the other; they're describing an escape hatch, not a ban.
This also changes how you should read an AI-suggested performance fix. A model that responds to a vague complaint that a component re-renders too much by wrapping three functions in useCallback is applying a fix from before the compiler existed. If the compiler is on, ask it to explain why a specific value still needs manual memoization. The compiler's own docs are careful to frame this as the exception, not the default: "in some cases developers may need more control over memoization" (react.dev, React Compiler introduction, accessed 4 September 2026).
How do you prompt for Actions instead of hand-rolled loading and error state?
React 19 introduced a convention, not a new syntax: any function that performs an async state transition is called an Action, and a family of hooks (useActionState, useOptimistic, plus useTransition's pending state) exist to support that convention instead of you wiring isLoading, error, and try/catch by hand every time. React's release notes describe the payoff directly: "Actions automatically manage submitting data for you", with pending state, error handling through Error Boundaries, and automatic reverting of optimistic updates all included rather than hand-wired.
The pattern a model defaults to, absent instruction, still looks like this:
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
async function handleSubmit() {
setIsLoading(true);
try {
await updateName(name);
} catch (err) {
setError(err);
}
setIsLoading(false);
}
Nothing here is broken, but it's the pattern Actions exist to replace:
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const result = await updateName(formData.get("name"));
if (result.error) return result.error;
return null;
},
null,
);
Naming the convention matters more than naming the hook, because the hook you want depends on what you're building: useActionState for a submission that needs a result and a pending flag, useOptimistic when you want to show the change before the server confirms it. Tell the model: this is a submission, use an Action, don't hand-roll isLoading and error state. It's also worth being precise about one distinction models routinely blur: an Action can run entirely client-side; a Server Action is the specific case of an Action marked to execute on the server. If your prompt says Server Action when it means plain Action, you'll get server-only code for a button that never needed a server round trip.
What should a complete React prompt actually contain?
Everything above compresses into a template you reuse across a project rather than rebuild per request. The version and convention lines do most of the work; the rest is context a model can't guess.
CONTEXT
React version: <19.2, or whatever you're actually on>
React Compiler enabled: <yes — don't add manual memoization unless asked
| no — memoize expensive calculations as usual>
Component library: <name it, or "plain HTML elements + our CSS">
State management: <useState/useReducer only | a specific library>
Server Components in use: <yes, this file is a Server Component
| no, this is a Client Component>
DEFAULTS TO FOLLOW
- Target React 19+. Refs are a plain `ref` prop; never wrap new
components in forwardRef.
- Don't put a value in state if it can be calculated from props or
state you already have. Calculate it inline during render.
- For any submission or async mutation, use an Action
(useActionState / useOptimistic), not hand-rolled isLoading + error
state, unless I say otherwise.
- If data fetching happens in an Effect, include a cleanup flag to
guard against out-of-order responses.
- Follow the Rules of Hooks: hooks only at the top level, never
inside a condition, loop, or after an early return.
OUTPUT
- The component code.
- A one-line note on any place you deviated from the defaults above,
and why.
That last line is doing real work: a model that silently reverts to a hand-rolled pattern under a specific constraint is more useful than one that does it without saying so. If you also want the model to build its own worked example before tackling something unfamiliar in your codebase, a pattern distinct from just handing it few-shot examples yourself, see our post on analogical prompting, which covers exactly that technique. And because a prompt like this is only as good as the context behind it, prompting for coding agents generally is worth reading alongside this one: the React-specific defaults above are one instance of a broader discipline about what context a coding agent actually needs.
What can't a model verify about your hooks code, just by reading it?
Whether it actually obeys the Rules of Hooks in every branch, and whether the compiler can safely optimize it. Both of those are mechanical properties, not judgment calls, and a model reasoning in prose about its own code is not the same thing as running the tool that checks it.
The real gate is the same one your build already has, or should: compiler-powered lint rules that "ship in eslint-plugin-react-hooks's recommended and recommended-latest preset" (React blog, 7 October 2025, accessed 4 September 2026). React Compiler itself also has documented bail-out behavior: code that breaks the Rules of Hooks doesn't get silently miscompiled, it gets skipped for that component, which is safer than either extreme but still means a hooks-rule violation quietly loses you the compiler's optimization rather than failing loudly.
None of this is a reason to hand-assemble the prompt above from scratch every time you open a new file. Prompt Architects is a prompt-enhancement layer (a web app, browser extensions, and an MCP server your coding tool can call directly) built to keep exactly this kind of project context, your React version, your compiler status, your state conventions, as a reusable brief instead of something you retype into every chat. There's a free plan, and it doesn't touch your repository or run any code; it just keeps the brief ready the next time you ask.
Stop rewriting prompts. Start shipping.
Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.
Create An Account