Org Skills

react-development

Write production-quality React code day to day — components, hooks, props and TypeScript typing, forms, data fetching with TanStack Query or Server Components, effects done right, styling, accessibility, error and loading states, and idiomatic React 19 features (actions, use, ref as prop, React Compiler).

Download .zip Raw Source
When agents use itUse this whenever the user asks to build, implement, refactor, or fix a React component, hook, page, form, modal, table, or feature; asks "how do I do X in React"; pastes React code with a bug; or asks about useEffect, custom hooks, controlled inputs, keys, context, Suspense, or Next.js client/server components. Also use it when generating any new React code, even if the request is small.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill react-development -a github-copilot
# or with the org installer (adds .github/skills/react-development):
npx -y github:AGCO-Global/org-skills add skill react-development

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill react-development
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill react-development -g

Installs the frontend-react-skills plugin, which bundles all Frontend / React skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-react-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill react-development -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill react-development -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill react-development -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill react-development -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

React Development

This is the implementation layer: how a senior engineer writes a component, a hook, or a feature so it is correct the first time and boring to review. Architecture decisions live in react-architecture; this skill assumes those are made.

1. Before writing a component

Answer in one line each (in your head or in the reply):

  1. What state does it own, and what does it receive? Owned state → useState; server state → query hook; URL state → router.
  2. Server or client? (Next.js) Default server; add "use client" only if it uses state, effects, browser APIs or event handlers.
  3. What are its states? loading, empty, error, partial, success, disabled — each gets rendered UI, not a null.
  4. What's its accessible role? A clickable div is never the answer.

2. Component template

import { type ComponentPropsWithoutRef } from 'react';

type OrderRowProps = {
  order: Order;
  onSelect: (id: Order['id']) => void;
  selected?: boolean;
} & Omit<ComponentPropsWithoutRef<'li'>, 'onSelect'>;

export function OrderRow({ order, onSelect, selected = false, ...rest }: OrderRowProps) {
  const total = formatMoney(order.total, order.currency);   // derive during render, don't store

  return (
    <li aria-selected={selected} {...rest}>
      <button type="button" onClick={() => onSelect(order.id)} aria-label={`Open order ${order.number}`}>
        <span>{order.number}</span>
        <span>{total}</span>
      </button>
    </li>
  );
}

Conventions:

  • Function declaration, named export, file = component name. No React.FC, no default exports.
  • Props type exported when other code composes it; spread ...rest onto the root element so consumers can pass className, data-*, aria-*.
  • Destructure props with defaults in the signature; no props.x in the body.
  • Derive, don't store. Anything computable from props/state is computed in render (or useMemo if expensive). Storing derived values in state + syncing with effects is the #1 React bug source.
  • Early returns for states: if (isPending) return <Skeleton/>; keep the happy path unindented at the bottom.
  • Keys from stable ids. Index keys only for static, never-reordered lists.
  • Event handlers named handleX inside, props named onX. Handlers that need no closure state can be defined outside the component.
  • Children over render props; render props over renderX props.
  • Conditional classes with clsx/cn; no string concatenation.
  • Component > ~150 lines or > 3 pieces of state → extract a child or a hook.

3. Hooks

Rules of thumb:

  • Custom hook when logic is reused or a component's effect+state cluster has a name (useDebouncedValue, useClickOutside, useOrderFilters). Name it after what it returns, not how it works.
  • Return an object for > 2 values, a tuple for [value, setter] pairs.
  • Hooks in features/<f>/hooks/ when feature-specific; shared/hooks/ when generic and framework-agnostic.

useEffect — the checklist before writing one:

You want to… Use instead
Compute something from props/state Derive in render / useMemo
Reset state when a prop changes key on the component, or derive
Respond to a user event The event handler
Fetch data TanStack Query / loader / Server Component
Notify parent of state change Call onChange in the handler
Subscribe to an external store useSyncExternalStore
Sync with a browser API / third-party widget / timer useEffect — this is what it's for; return a cleanup

When an effect is legitimate: one concern per effect, complete dependency array (don't silence the lint rule — restructure), cleanup for every subscription/timer/AbortController, and useEffectEvent (React 19) for reading latest values without re-subscribing.

useRef for DOM handles and mutable values that must not trigger renders (timers, previous value). React 19: ref is a normal prop — no forwardRef needed in new code.

4. Data fetching

Client (TanStack Query):

export function useOrders(filters: OrderFilters) {
  return useQuery({
    queryKey: orderKeys.list(filters),
    queryFn: ({ signal }) => api.orders.list(filters, { signal }),
    staleTime: 30_000,
    placeholderData: keepPreviousData,
  });
}
  • One hook per query in features/<f>/api/; components never call useQuery inline.
  • Mutations: useMutation with onSuccess: () => queryClient.invalidateQueries({ queryKey: orderKeys.all }); optimistic updates only where latency is user-visible and rollback is implemented.
  • Pass signal to fetch for cancellation.
  • Handle isPending, isError (with error typed), and empty data.

Server (Next.js RSC): async function Page() awaits data directly; wrap slow sections in <Suspense fallback>; pass plain serialisable props to client components; mutations via Server Actions with useActionState and revalidateTag.

React 19 use(): read a promise passed from a server component inside a client component wrapped in Suspense — for streaming, not for ad-hoc fetching.

5. Forms

  • React Hook Form + Zod (zodResolver). Schema is the single source of validation truth; infer the TS type from it.
  • Inputs uncontrolled via register; controlled Controller only for custom components.
  • Field errors rendered next to the field with aria-invalid and aria-describedby; summary role="alert" on submit failure.
  • Submit: disable button while isSubmitting, catch server errors, map them to fields with setError, reset on success when appropriate.
  • Server Actions (Next.js): useActionState for progressive enhancement; still validate with the same Zod schema on the server.
  • Never e.target.value into useState for each field of a real form — that's what RHF replaces.

6. Context

  • For dependency injection and rarely-changing values (theme, auth session, feature flags, a store instance).
  • Provide a useX() hook that throws if used outside the provider.
  • Memoise the value object; split contexts by change frequency.
  • Not for frequently-changing state — use Zustand/Jotai selectors so consumers subscribe to slices.

7. Styling

  • Tailwind: utilities in JSX, extract with cva for variant-heavy components, cn() to merge; no @apply soup.
  • CSS Modules: one module per component, class names camelCase, tokens via CSS variables.
  • Never inline style={{}} for static styles; only for truly dynamic values (computed widths, CSS vars).
  • Respect prefers-reduced-motion; animate with CSS/motion not JS timers.

8. Accessibility defaults (every component)

  • Semantic elements: <button>, <a href>, <nav>, <main>, <ul>; <div onClick> is a defect.
  • Every input labelled; icon buttons get aria-label; images alt.
  • Focus is visible and managed (modals trap and restore focus; use Radix/React Aria primitives rather than hand-rolling).
  • Async feedback in role="status"/role="alert".
  • Colour contrast ≥ 4.5:1; hit targets ≥ 24 px.

9. Errors, loading, and edge cases

  • Route/segment-level ErrorBoundary with a retry action; component-level boundaries around third-party widgets.
  • Skeletons match final layout dimensions (prevents CLS); spinners only for < 300 ms actions.
  • Empty states have copy and a primary action.
  • Guard against: undefined data on first render, race conditions (stale response → cancelled via signal or ignored via query keys), double submit, unmounted setState (AbortController cleanup).
  • Log unexpected errors to the monitoring client; never console.error and swallow.

10. TypeScript idioms

  • Infer where possible; annotate function boundaries and exported types.
  • satisfies for config; as const for literal unions; discriminated unions for component variants.
  • Event types: React.ChangeEvent<HTMLInputElement>, React.MouseEvent<HTMLButtonElement>.
  • Generic components: function Table<T extends { id: string }>(props: TableProps<T>) — no FC.
  • No any; unknown + narrowing at boundaries; Zod-parse untrusted JSON.

11. Definition of done for a component

  • All UI states rendered (loading/empty/error/success)
  • Keyboard-operable, labelled, roles correct
  • No derived state stored; no effect that could be a computation or handler
  • Types exported, no any
  • Story or test covering the main states (see react-testing)
  • Under ~150 lines or split with reason

Anti-patterns to fix on sight

  • useEffect(() => setX(f(props)), [props]) — derive instead.
  • useEffect fetch with useState loading flags.
  • Mutating state (arr.push then setArr(arr)).
  • Index keys on dynamic lists; Math.random() keys.
  • useCallback/useMemo everywhere with no memoised consumer.
  • forwardRef in new React 19 code; defaultProps; class components.
  • dangerouslySetInnerHTML with unsanitised content.
  • Prop drilling a setter four levels down.
  • Business logic (pricing, permissions) inside JSX.