# Pathrule Pattern: React + TypeScript (1.0.0)
# ::pathrule:package:react-typescript

### [RULE] Do not sync derived state or fetch data inside effects  (path: /src/components)
<!-- scope: folder | priority: high | advisory -->

Effects synchronize a component with an external system (DOM nodes, browser APIs, third-party widgets, subscriptions). They are not for deriving state from props/state or for fetching data. The official `eslint-plugin-react-hooks` recommended preset flags `set-state-in-effect` for this reason.

Do not:

- Mirror props/state into state with an effect plus `setState`. Compute it during render: `const visible = items.filter(i => i.active)`. Wrap only genuinely expensive work in `useMemo`.
- Reset state on a prop change with an effect. Pass a `key` so React remounts the subtree with fresh state: `<Profile userId={userId} key={userId} />`.
- Run logic that should happen because of a user action inside an effect. Put it in the event handler, which knows what the user actually did.
- Fetch data in a bare effect. This causes duplicate requests, loading flicker on every navigation, and StrictMode double-fetch in dev. Use a data layer (TanStack Query, RTK Query, the framework's server data layer / Server Components, or the `use()` hook with a cached promise).

If you keep an effect, it must subscribe to something outside React and return a cleanup function. Prefer `useSyncExternalStore` for external stores.

---

### [RULE] Interactive elements must be accessible  (path: /src/components)
<!-- scope: folder | priority: high | advisory -->

A `div` or `span` with an `onClick` is not a button. It is unreachable by keyboard, invisible to screen readers, and a real defect for affected users.

- Reach for native `button`, `a`, `label`, `input`, `select` before any custom control. A `button` gives you focus, Enter/Space activation, and the correct role for free.
- Every control has an accessible name (visible text, `aria-label`, or an associated `<label htmlFor>`), a visible `:focus-visible` state, and full keyboard operability.
- Associate inputs with labels using `useId()` for the `id`/`htmlFor` pair so ids stay stable and hydration-safe.
- Images that convey meaning have `alt`; decorative images have `alt=""`. Never use color as the only signal.
- If you must build a custom widget, follow the matching ARIA Authoring Practices pattern (role, states, key handling) in full rather than partially.

---

### [MEMORY] How we type component props  (path: /src/components)

Conventions for typing components in this codebase.

- Type props with an explicit `type` or `interface` on a plain function component. We do not use `React.FC`: it adds an implicit `children` even for components that take none and complicates generics.
- No `any`. When a shape is truly unknown use `unknown` and narrow. Type event handlers precisely (`React.ChangeEvent<HTMLInputElement>`, `React.MouseEvent<HTMLButtonElement>`).
- Derive types from a single source of truth (Zod schema, generated API types, a const object with `as const`) instead of restating shapes that can drift apart.
- For components with mutually exclusive modes, model props as a discriminated union on a literal discriminant rather than a bag of optional props. Example: `{ status: 'error'; message: string; onRetry: () => void } | { status: 'success'; message: string }`. This makes invalid prop combinations a compile error instead of a runtime check.
- Under `verbatimModuleSyntax: true` (our tsconfig), type-only imports must use `import type { Props } from './x'`. Mixing a value and a type in one statement without the modifier is an error; the lint autofix handles most of it.

---

### [MEMORY] React Compiler handles memoization for new code  (path: /src)

React Compiler 1.0 (stable, Oct 2025) auto-memoizes components and hooks at build time. This changes how we optimize here.

- New code: write components without `useMemo`, `useCallback`, or `React.memo`. The compiler inserts memoization where it helps. Reaching for these manually is usually noise and can fight the compiler.
- Keep `useMemo` only for a measured expensive computation, or where a stable reference is semantically required (e.g. a value passed to a non-React API or a dependency array the compiler cannot see through, such as some third-party hooks).
- Do not delete existing manual memoization to "modernize" a file. The compiler's `preserve-manual-memoization` rule expects it left in place; removing it can change behavior. Migrate deliberately, not opportunistically.
- The compiler relies on the Rules of React. Run the `eslint-plugin-react-hooks` recommended (or recommended-latest) preset; its rules (`rules-of-hooks`, `exhaustive-deps`, `set-state-in-effect`, `purity`, `refs`) are how the compiler surfaces violations. Code that breaks these rules silently opts out of optimization.
- Performance work starts with the React DevTools profiler, not with sprinkling memo hooks.

---

### [MEMORY] Component structure and state placement  (path: /src/components)

How we structure components so the tree stays predictable.

- One responsibility per component. When a component grows a second job (fetching + layout + a modal), split it.
- Colocate state with the component that owns it. Lift state up only when two siblings genuinely need the same value; do not hoist to a parent or context "just in case".
- Separate presentational components (props in, JSX out) from components that do data access or own significant state. Presentational components are trivial to test and reuse.
- Use lazy `useState` initialization for expensive initial values: `useState(() => readFromStorage())`, not `useState(readFromStorage())`, so the work runs once instead of every render.
- Custom hooks encapsulate reusable stateful logic and are named `useX`. Keep them focused; a hook that returns ten unrelated things is a refactor signal.
- Name files and exports consistently (one component per file, file name matches the export) so navigation is mechanical.

---

### [SKILL] react-component-review  (path: /)

---
name: react-component-review
description: Review a React + TypeScript component for prop types, accessibility, hook correctness, and effect/memoization discipline before merge.
---

# React component review

Run this before approving a new or changed component.

## Types
- [ ] Props are explicitly typed; no `any` (use `unknown` + narrowing if truly unknown)
- [ ] Variant/mode components use a discriminated union, not a pile of optional props
- [ ] Types derive from a single source of truth (schema / generated API types), not restated shapes
- [ ] Type-only imports use `import type` (verbatimModuleSyntax)

## Effects and state
- [ ] No effect that only computes derived state from props/state (compute during render)
- [ ] No effect resetting state on a prop change (use a `key` instead)
- [ ] No data fetching in a bare effect (use the data layer / Server Components / `use()`)
- [ ] Any remaining effect syncs an external system and has a cleanup function
- [ ] Hooks are called unconditionally at the top level; dependency arrays are honest

## Memoization
- [ ] No reflexive `useMemo`/`useCallback`/`React.memo` in new code (compiler handles it)
- [ ] Existing manual memoization left intact, not stripped
- [ ] `eslint-plugin-react-hooks` (recommended preset) passes with no disables

## Accessibility
- [ ] Interactive elements use native semantics (`button`, `a`, `input`, `label`)
- [ ] Every control has an accessible name, visible focus, and keyboard operability
- [ ] Inputs are associated to labels via a stable `useId`
- [ ] Images have correct `alt`; color is not the only signal

## Structure
- [ ] One clear responsibility; reasonable size
- [ ] State colocated, lifted only when genuinely shared
- [ ] Expensive initial state uses lazy `useState(() => ...)`
