agentleFS
reddygopalkrishnareddygopalkrishna/developer-agent-skills/skills/react-patterns/SKILL.md

react-patterns

Skill8 starsChanged 8 days agoMITView on GitHub

Writes and reviews idiomatic React - minimal state with derived values, effects only for external systems, server state through a data library or framework loaders, correct keys, composition over prop drilling, measured memoization, error boundaries, Suspense, forms and actions, and the Server/Client Component boundary. Use when building or reviewing React components or hooks, fixing re-render, stale state or useEffect bugs, structuring state, data fetching and forms, or testing components in React, Next.js, React Router/Remix or Vite apps.

38 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use typescript-strict, docker-expert and 7 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • MicrosoftSkill

    typescript-strict

    Enable TypeScript strict mode (strict, noImplicitAny, strictNullChecks, etc.) in a project and fix the resulting errors without weakening types, tests, or runtime behavior. Use when asked to "enable strict mode", "turn on TypeScript strict", "fix strict errors", or migrate a codebase to strict type checking.

    Also in 3 projects that use this

  • xfstudioSkill

    docker-expert

    Docker containerization expert with deep knowledge of multi-stage builds, image optimization, container security, Docker Compose orchestration, and production deployment patterns. Use PROACTIVELY for Dockerfile optimization, container issues, image size problems, security hardening, networking, and orchestration challenges.

    Also in 3 projects that use this

  • LangChainSkill

    docker-patterns

    Best practices for Docker containerization and multi-stage builds

    Also in 4 projects that use this

  • BlockSkill

    testing-strategy

    Guidelines for writing effective tests in this project

    Also in 4 projects that use this

  • MicrosoftSkill

    error-handling

    统一错误处理系统。在添加 API 端点、修改错误处理、添加前端 API 调用、编写错误相关测试时使用。

    Also in 5 projects that use this

  • HABUBUSSSkill

    backend-patterns

    Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.

    Also in 5 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. 0 are common, 9 are distinctive, and 8 are very specific to this project.

  • 0

    Common

    Most of the 34 edited versions we read keep these.

  • 9

    Distinctive

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Not for: React Native platform APIs, or styling questions.

    line 17
  2. Very specific:

    Check the versions: Read package.json for the react version and the framework (Next.js App Router or Pages Router, React Router v7 / Remix, …

    line 21
  3. Distinctive:

    Every effect that starts something must stop it:

    line 60
  4. Distinctive:

    Use a stable, unique ID from the data.

    line 81
  5. Distinctive:

    Don't use the array index for lists that can reorder, insert or delete.

    line 82
  6. Very specific:

    Never generate keys during render (Math.random(), crypto.randomUUID()).

    line 83
  7. Very specific:

    Never switch one input between controlled and uncontrolled (value={undefined} then a string).

    line 92
  8. Distinctive:

    When context is right:

    line 104
  9. Very specific:

    Put an error boundary at each route and around independent widgets so one failure doesn't blank the page.

    line 127
  10. Very specific:

    Use <Suspense fallback={...}> with data sources that support it (framework loaders, useSuspenseQuery, use(promise) in React 19).

    line 129
  11. Distinctive:

    Place Suspense boundaries where a loading state makes sense to the user, not around every component.

    line 130
  12. Very specific:

    Only applies in frameworks with React Server Components (for example the Next.js App Router).

    line 134
  13. Very specific:

    Import the server-only package in modules with secrets or DB access so they fail the build if imported into client code.

    line 139
  14. Very specific:

    Use findBy* for content that appears asynchronously.

    line 191
  15. Distinctive:

    Test what the user sees and does, not state variables or hook internals.

    line 193
  16. Distinctive:

    No effect exists that only transforms data or responds to a user event.

    line 209
  17. Distinctive:

    Every effect that subscribes, connects or starts a timer has cleanup.

    line 210

“Common” is measured: kept by at least 6 in 10 of the 34 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

React Patterns

UI is a function of state. Keep the minimum state, compute everything else during render, and use effects only to synchronize with systems outside React. Most React bugs come from duplicated state and effects that should not exist.

When to use

  • Writing or reviewing components, custom hooks or a React diff.
  • Bugs: stale values, infinite re-render loops, flicker, "state is one step behind", lost input focus, slow typing.
  • Deciding where state, data fetching or form handling should live.

Not for: React Native platform APIs, or styling questions.

Workflow

  1. Check the versions. Read package.json for the react version and the framework (Next.js App Router or Pages Router, React Router v7 / Remix, Vite SPA, Expo). React 19 APIs (actions, use, ref as a prop, <Context value>) need react 19+. Server Components need a framework that supports them.
  2. Check for React Compiler (babel-plugin-react-compiler, or the framework's compiler option). If it is on, don't add manual useMemo/useCallback/memo unless profiling shows a need. Check the installed version's docs for what it covers.
  3. Classify every piece of data using the table below before writing state.
  4. Audit every useEffect against references/effects.md. Delete the ones that don't sync with an external system.
  5. Lint with eslint-plugin-react-hooks. Fix exhaustive-deps warnings by restructuring code; never suppress them.
  6. Test behavior with Testing Library (see Testing).

Where state lives

KindExamplePut it in
Server datausers, ordersA data library (TanStack Query, SWR, RTK Query, Apollo, Relay) or framework loaders / Server Components. Never copy it into useState
URL statefilters, sort, tab, pageSearch params or route params, so it survives reload and can be shared
Form stateinputs before submitUncontrolled inputs plus FormData, or a form library for complex validation
Local UI stateopen/closed, hoveruseState in the lowest component that needs it
Shared client statetheme, current user, cartContext for low-frequency values; a store with selectors (Zustand, Redux Toolkit, Jotai) for frequently changing ones
Derived valuesfiltered list, totals, validityCompute during render; useMemo only if measured as expensive

Lift state to the closest common parent of the components that need it, and no higher.

Rules

Derive, don't sync

// Bad: duplicated state kept in sync by an effect (extra render, can drift)
const [fullName, setFullName] = useState("");
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);

// Good
const fullName = `${first} ${last}`;

Store IDs, not copies of objects: keep selectedId and look up the item, so edits to the list are never shadowed by a stale copy.

Effects are for external systems

Valid uses: subscriptions (WebSocket, browser events, matchMedia), third-party widgets and manual DOM integration, timers, and fetching data when no library or framework loader is available. Everything else (transforming data, reacting to a click, resetting state on a prop change, notifying a parent) belongs in render or in an event handler. See references/effects.md.

Every effect that starts something must stop it:

useEffect(() => {
  const socket = connect(roomId);
  socket.on("message", onMessage);
  return () => socket.disconnect();
}, [roomId]);

Strict Mode runs effects twice in development to expose missing cleanup. Fix the cleanup; don't remove Strict Mode.

Reset state with a key

// Remounts the form, clearing its state, whenever the user changes
<ProfileForm key={user.id} user={user} />

Keys in lists

  • Use a stable, unique ID from the data.
  • Don't use the array index for lists that can reorder, insert or delete; state and focus attach to the wrong row.
  • Never generate keys during render (Math.random(), crypto.randomUUID()); every render remounts every row.

Controlled vs uncontrolled inputs

UseWhen
Uncontrolled (defaultValue, read with FormData or a ref)Most forms: values are only needed on submit
Controlled (value + onChange)The UI must react to every keystroke: live validation, formatting, dependent fields

Never switch one input between controlled and uncontrolled (value={undefined} then a string). Initialize with "".

Composition before context

Pass components as children or slot props instead of threading props through layers that don't use them.

<Layout sidebar={<UserMenu user={user} />}>
  <Dashboard user={user} />
</Layout>

When context is right:

  • Split contexts by how often they change (theme vs live cursor position). Every consumer re-renders when the value changes.
  • Memoize object values so consumers don't re-render on every provider render:
const value = useMemo(() => ({ user, signOut }), [user, signOut]);
return <AuthContext value={value}>{children}</AuthContext>; // React 18: AuthContext.Provider
  • For high-frequency shared state, use a store with selectors instead of context.

Memoize only what you measured

Profile first with the React DevTools Profiler. Reach for useMemo, useCallback or memo when:

  • a computation is measurably slow (filtering or sorting thousands of items on every keystroke),
  • a memoized child or an effect dependency needs a stable reference.

Wrapping everything adds noise and hides real problems. For slow typing, try useDeferredValue or startTransition for the expensive part, and virtualize long lists.

Error boundaries and Suspense

  • Put an error boundary at each route and around independent widgets so one failure doesn't blank the page. Boundaries are class components (getDerivedStateFromError, componentDidCatch) or the react-error-boundary package.
  • Boundaries do not catch errors in event handlers, setTimeout or other async code; handle those with try/catch and show state.
  • Use <Suspense fallback={...}> with data sources that support it (framework loaders, useSuspenseQuery, use(promise) in React 19). A promise passed to use must come from a cache, a loader or a parent; one created during render is recreated on every render.
  • Place Suspense boundaries where a loading state makes sense to the user, not around every component.

Server and Client Components

Only applies in frameworks with React Server Components (for example the Next.js App Router).

  • Components are Server Components by default. "use client" at the top of a file makes it and everything it imports client code. Put the directive on small leaf components (a button, a chart), not on layouts.
  • Server Components cannot use state, effects, event handlers or browser APIs.
  • Props passed from server to client must be serializable. Pass Server Components into Client Components as children.
  • Import the server-only package in modules with secrets or DB access so they fail the build if imported into client code.
  • Functions marked "use server" are public HTTP endpoints. Validate their input and check authorization inside every one.

Forms and actions (React 19)

import { useActionState } from "react";

async function subscribe(prevState: { error: string | null }, formData: FormData) {
  const email = String(formData.get("email") ?? "");
  if (!email.includes("@")) return { error: "Enter a valid email address." };
  await api.subscribe(email);
  return { error: null };
}

export function SubscribeForm() {
  const [state, formAction, isPending] = useActionState(subscribe, { error: null });
  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      <button type="submit" disabled={isPending}>Subscribe</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}
  • useFormStatus (from react-dom) only works in a component rendered inside the <form>.
  • useOptimistic shows an expected result while the action runs and reverts on failure.
  • Validate on the server too; client checks are only for user experience.

Custom hooks

Extract a hook to share logic, not state: each call gets its own state. Name it useSomething, call hooks unconditionally at the top level, and return the smallest useful API.

Testing

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("adds a todo", async () => {
  const user = userEvent.setup();
  render(<Todos />);
  await user.type(screen.getByRole("textbox", { name: /new todo/i }), "Buy milk");
  await user.click(screen.getByRole("button", { name: /add/i }));
  expect(screen.getByRole("listitem")).toHaveTextContent("Buy milk");
});
  • Query in this order: getByRole (with name), getByLabelText, getByText, and getByTestId only as a last resort. Role queries also catch accessibility bugs.
  • Use findBy* for content that appears asynchronously; don't add manual waits.
  • Mock the network at the boundary (for example MSW), not the component's own hooks.
  • Test what the user sees and does, not state variables or hook internals.

Anti-patterns

  • Effect chains: one effect sets state that triggers another. Compute in render or do it all in the event handler.
  • Fetching in useEffect without cleanup: race conditions show stale results. Use a data library, or handle races as shown in the reference.
  • Server data copied into useState: it goes stale. Read it from the cache.
  • Objects or functions created in render used as effect dependencies: the effect runs every render. Move them inside the effect or outside the component.
  • Index or random keys on dynamic lists.
  • Defining a component inside another component: it remounts on every render and loses state and focus.
  • One giant context holding everything.
  • "use client" at the root layout: it turns the whole tree into client code.
  • Suppressing exhaustive-deps: it hides stale closures.

Done when

  • No effect exists that only transforms data or responds to a user event.
  • Every effect that subscribes, connects or starts a timer has cleanup.
  • Server state lives in a data library or framework loader, URL state lives in the URL.
  • Hooks lint passes with no suppressed warnings; tests query by role.
  • Any memoization added is backed by a profile or a stable-reference requirement.

References

  • references/effects.md — read when writing, reviewing or debugging any useEffect, or deciding between an effect, an event handler, render-time computation or useSyncExternalStore.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.