Source profileQuality 91/100

affaan-m/ECC/skills/react-patterns/SKILL.md

react-patterns

React 18/19 patterns including hooks discipline, server/client component boundaries, Suspense + error boundaries, form actions, data fetching, state management decision trees, and accessibility-first composition. Use when writing or reviewing React components.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Idiomatic React 18/19 patterns for building robust, accessible, performant component trees.

Best for

  • Use when writing or reviewing React components.

Not for

  • Tasks that require unconfirmed production actions or broad system permissions.
  • Environments where the pinned source and install steps cannot be inspected.

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/affaan-m/ECC --skill "skills/react-patterns"
Safe inspection promptEditorial

Inspect the Agent Skill "react-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/react-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    When to Activate

    Writing or modifying React function components, custom hooks, or component trees

    Writing or modifying React function components, custom hooks, or component treesReviewing JSX/TSX filesDesigning state shape or component composition
  2. 02

    Core Principles

    Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.

    Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.Effects, mutations, network calls, and subscriptions live in event handlers or useEffect — never in the render body.React has no inheritance model for components. Compose with children, render props, or component props.
  3. 03

    1. Render is a Pure Function of Props and State

    Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.

    Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.
  4. 04

    2. Side Effects Outside Render

    Effects, mutations, network calls, and subscriptions live in event handlers or useEffect — never in the render body.

    Effects, mutations, network calls, and subscriptions live in event handlers or useEffect — never in the render body.
  5. 05

    3. Composition Over Inheritance

    React has no inheritance model for components. Compose with children, render props, or component props.

    React has no inheritance model for components. Compose with children, render props, or component props.

Permission review

Static risk signals and limitations

No configured static risk pattern was detected

This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score91/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars234,327SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
affaan-m/ECC
Skill path
skills/react-patterns/SKILL.md
Commit
4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
License
MIT
Collected
2026-07-28
Default branch
main
View the original SKILL.md

React Patterns

Idiomatic React 18/19 patterns for building robust, accessible, performant component trees.

When to Activate

  • Writing or modifying React function components, custom hooks, or component trees
  • Reviewing JSX/TSX files
  • Designing state shape or component composition
  • Migrating class components or older forwardRef/useEffect-heavy code
  • Choosing between local state, lifted state, context, and external stores
  • Working with Server Components / Client Components (Next.js App Router, RSC)
  • Implementing forms with React 19 actions or controlled inputs
  • Wiring data fetching with TanStack Query / SWR / RSC

Core Principles

1. Render is a Pure Function of Props and State

// Good: derive during render
function Cart({ items }: { items: CartItem[] }) {
  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
  return <span>{formatMoney(total)}</span>;
}

// Bad: derived state stored separately
function Cart({ items }: { items: CartItem[] }) {
  const [total, setTotal] = useState(0);
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
  }, [items]);
  return <span>{formatMoney(total)}</span>;
}

Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.

2. Side Effects Outside Render

Effects, mutations, network calls, and subscriptions live in event handlers or useEffect — never in the render body.

3. Composition Over Inheritance

React has no inheritance model for components. Compose with children, render props, or component props.

Hooks Discipline

See rules/react/hooks.md for the full ruleset. Highlights:

  • Top-level only, never conditional
  • Cleanup every subscription, interval, listener
  • Functional updater (setX(prev => prev + 1)) when new state depends on old
  • Default position: do not memoize — add useMemo/useCallback only when a profiler or a dependency chain proves it matters
  • Extract a custom hook only when the same hook sequence appears in 2+ components

State Location Decision Tree

Used by one component?
  -> useState inside it

Used by parent + a few descendants?
  -> lift to nearest common ancestor

Used across distant branches AND low-frequency reads (theme, auth, locale)?
  -> React Context

High-frequency updates shared across the tree?
  -> external store (Zustand, Jotai, Redux Toolkit)

Derived from a server?
  -> server-state library (TanStack Query, SWR, RSC fetch)

Most pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful.

Server / Client Components (RSC)

// Server Component - default, async, never ships JS for itself
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await db.product.findUnique({ where: { id: params.id } });
  if (!product) notFound();
  return <ProductView product={product} />;
}

// Client Component - opt in with "use client"
"use client";
export function AddToCartButton({ productId }: { productId: string }) {
  const [pending, startTransition] = useTransition();
  return (
    <button
      disabled={pending}
      onClick={() => startTransition(() => addToCart(productId))}
    >
      {pending ? "Adding..." : "Add to cart"}
    </button>
  );
}

Boundaries:

  • Server -> Client: pass serializable props or children
  • Client -> Server: invoke Server Actions via <form action={...}> or imperatively from event handlers
  • Never import a Server Component from a Client Component file — compose them via children instead

Suspense + Error Boundaries

<ErrorBoundary fallback={<ErrorView />}>
  <Suspense fallback={<UserSkeleton />}>
    <UserDetail id={id} />
  </Suspense>
</ErrorBoundary>
  • Place Suspense boundaries close to the data, not at the route root — progressively reveal content
  • Error Boundary remains a class API; use react-error-boundary for a hook-friendly wrapper
  • A boundary catches errors thrown during render, lifecycle, and constructors of its children — NOT in event handlers or async code

Forms

React 19 form actions (preferred for new code)

"use client";
import { useActionState } from "react";

const initial = { error: null as string | null };

async function updateUserAction(_prev: typeof initial, formData: FormData) {
  "use server";
  const parsed = UserSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: "Invalid input" };
  await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });
  return { error: null };
}

export function UserForm() {
  const [state, formAction, pending] = useActionState(updateUserAction, initial);
  return (
    <form action={formAction}>
      <input name="name" required />
      <button type="submit" disabled={pending}>Save</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}

Controlled inputs

Use controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.

Complex forms

For multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap.

Data Fetching Decision Matrix

NeedTool
Per-request data in Next.js App RouterRSC await fetch()
Client-side cache + mutations + invalidationTanStack Query
Lightweight client cache + revalidationSWR
Real-time subscriptionsServer-Sent Events, WebSockets, or the lib's subscription API
One-off fire-and-forgetfetch() in an event handler

Avoid useEffect + fetch for application data — race conditions, no cache, no retry, no Suspense integration.

Composition Recipes

Slot via children

<Layout>
  <Header />
  <Main>{content}</Main>
</Layout>

Named slots

<Page header={<Nav />} sidebar={<Filters />}>
  <Results />
</Page>

Compound components (shared state via Context)

<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Trigger value="profile">Profile</Tabs.Trigger>
    <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panel value="profile"><Profile /></Tabs.Panel>
  <Tabs.Panel value="settings"><Settings /></Tabs.Panel>
</Tabs>

Render prop / function-as-child

Useful when the parent needs to pass parameters to the rendered output:

<DataLoader id={id}>
  {({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />}
</DataLoader>

Modern alternative: a hook (useData(id)) returning the same shape — usually cleaner.

Performance

When React.memo Actually Helps

Wrap a component in React.memo only when:

  1. It re-renders frequently
  2. Its props are usually the same between renders
  3. Its render is measurably expensive

React.memo adds an equality check on every render. If props differ on most renders, the check is pure overhead.

Avoiding Render Cascades

  • Lift state down rather than up where possible
  • Split context: one context per concern, so a change to themeContext does not re-render auth consumers
  • Use useSyncExternalStore for external state libraries — required for safe concurrent rendering

Lists

  • Provide stable key props (database id, not array index)
  • Virtualize long lists with @tanstack/react-virtual or react-window once visible item count exceeds ~50 with non-trivial rows

Accessibility-First Composition

  • Always render semantic HTML (<button>, <a>, <nav>, <main>) before reaching for role attributes
  • Every interactive element must be reachable by keyboard
  • Form inputs need labels — <label htmlFor> or aria-label if visually labeled by an icon
  • Manage focus on route changes and modal open/close
  • Run axe in component tests (see skills/react-testing)
  • Cross-link: skills/accessibility/SKILL.md covers WCAG criteria and pattern libraries

Routing

This skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core.

Out of Scope (Pointer Sections)

  • Next.js specifics: App Router data loading, Route Handlers, Middleware, Parallel Routes — separate concern, use Next.js docs
  • React Native: Platform-specific patterns differ enough to warrant a separate react-native-patterns skill (not present yet)
  • Remix: Loader/action conventions overlap with RSC but follow Remix docs

Related

  • Rules: rules/react/ — coding-style, hooks, patterns, security, testing
  • Skills: react-performance for the Vercel-derived performance ruleset, frontend-patterns for cross-framework UI concerns, accessibility, angular-developer for framework comparison
  • Agents: react-reviewer for code review, react-build-resolver for build/bundler errors
  • Commands: /react-review, /react-build, /react-test

Examples

Custom hook for debounced search

function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

function SearchBox() {
  const [query, setQuery] = useState("");
  const debounced = useDebounce(query, 300);
  const { data } = useQuery({
    queryKey: ["search", debounced],
    queryFn: () => searchApi(debounced),
    enabled: debounced.length > 0,
  });
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results items={data ?? []} />
    </>
  );
}

Optimistic UI with React 19 useOptimistic

"use client";
import { useOptimistic } from "react";

export function MessageList({ messages }: { messages: Message[] }) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage: Message) => [...state, newMessage],
  );

  async function send(formData: FormData) {
    const text = String(formData.get("text"));
    addOptimistic({ id: "pending", text, sender: "me" });
    await saveMessage(text);
  }

  return (
    <>
      <ul>{optimistic.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
      <form action={send}>
        <input name="text" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

Splitting context to avoid render cascades

// Two contexts: one rarely changes, one frequently
const ThemeContext = createContext<Theme>("light");
const NotificationsContext = createContext<Notification[]>([]);

// A component that only consumes ThemeContext does NOT re-render when notifications change

Alternatives

Compare before choosing