web-framework-react ยท diff
git:20260202.b236384 to git:20260316.53fded1
142 added, 416 removed. Audit A to A.
---
name: web-framework-react
description: Component architecture, hooks, patterns
---
# React Components
- > **Quick Guide:** Tiered components (Primitives -> Components -> Patterns -> Templates). React 19: pass `ref` as a prop directly (no `forwardRef` needed). `asChild` pattern for polymorphic components. Expose `className` prop for styling flexibility. lucide-react for icons.
+ > **Quick Guide:** Tiered components (Primitives -> Components -> Patterns -> Templates). React 19: pass `ref` as a prop directly (no `forwardRef` needed). Expose `className` prop for styling flexibility. Use `useActionState` for forms, `useOptimistic` for instant feedback, `use()` for conditional promise/context reading. Ref callbacks can return cleanup functions.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST pass `ref` as a regular prop in React 19 - `forwardRef` is deprecated)**
**(You MUST expose `className` prop on ALL reusable components for customization)**
- **(You MUST use named constants for ALL numeric values - NO magic numbers)**
-
- **(You MUST use named exports - NO default exports in component libraries)**
-
**(You MUST use `useActionState` for form submissions with pending/error state)**
+ **(You MUST call `useFormStatus` from a child component inside `<form>`, NOT in the component that renders the form)**
+
</critical_requirements>
---
- **Auto-detection:** React 19, components, hooks, use(), useActionState, useFormStatus, useOptimistic, Actions, ref as prop
+ **Auto-detection:** React 19, components, hooks, use(), useActionState, useFormStatus, useOptimistic, Actions, ref as prop, ref cleanup, forwardRef migration, component variants, error boundary
**When to use:**
- - Building React components
- - Implementing component architecture patterns
- - Working with icons in components
- - Creating custom hooks
- - Handling form submissions with Actions
-
- **Key patterns covered:**
-
- - Component architecture tiers
- - React 19 ref as prop (replaces forwardRef)
- - React 19 hooks: `use()`, `useActionState`, `useFormStatus`, `useOptimistic`
- - Actions API for form handling
- - Icon usage with lucide-react
- - Custom hooks for common patterns
- - Error boundaries with retry
+ - Building React components with type-safe props
+ - Migrating from forwardRef to React 19 ref-as-prop
+ - Handling form submissions with React 19 Actions API
+ - Creating custom hooks for reusable logic
+ - Implementing error boundaries with retry
**When NOT to use:**
- Simple one-off components without variants (skip variant abstractions)
- - Static content without interactivity (consider static HTML)
+ - Static content without interactivity
- **Detailed Resources:**
+ **Key patterns covered:**
- - For code examples, see [examples/core.md](examples/core.md)
- - For decision frameworks and anti-patterns, see [reference.md](reference.md)
+ - Component architecture tiers and variant props
+ - React 19 ref as prop (replaces forwardRef)
+ - React 19 hooks: `use()`, `useActionState`, `useFormStatus`, `useOptimistic`
+ - Ref callback cleanup functions
+ - Error boundaries with retry and custom fallbacks
+ - Custom hooks (pagination, debounce, localStorage)
+ - Event handler naming conventions
---
<philosophy>
## Philosophy
- React components follow a tiered architecture from low-level primitives to high-level templates. Components should be composable, type-safe, and expose necessary customization points (`className`, refs). Use variant abstractions only when components have multiple variants to avoid over-engineering. React is styling-agnostic - apply styles via the `className` prop.
+ React components follow a tiered architecture from low-level primitives to high-level templates. Components should be composable, type-safe, and expose necessary customization points (`className`, refs). Use variant abstractions only when components have multiple variant dimensions to avoid over-engineering. React is styling-agnostic -- apply styles via the `className` prop.
- **React 19 Changes:** In React 19, `forwardRef` is deprecated. Pass `ref` as a regular prop directly. New hooks (`use()`, `useActionState`, `useFormStatus`, `useOptimistic`) simplify data fetching and form handling with the Actions API.
+ **React 19 Changes:** `forwardRef` is deprecated -- pass `ref` as a regular prop directly. New hooks (`use()`, `useActionState`, `useFormStatus`, `useOptimistic`) simplify data fetching and form handling with the Actions API. Ref callbacks can return cleanup functions, eliminating the need for separate `useEffect` cleanup.
</philosophy>
---
<patterns>
## Core Patterns
### Pattern 1: Component Architecture Tiers
- React components are organized in a tiered hierarchy from low-level building blocks to high-level page layouts.
-
- #### Tier Structure
+ Components are organized in a tiered hierarchy:
1. **Primitives** (`src/primitives/`) - Low-level building blocks (skeleton)
2. **Components** (`src/components/`) - Reusable UI (button, switch, select)
3. **Patterns** (`src/patterns/`) - Composed patterns (feature, navigation)
4. **Templates** (`src/templates/`) - Page layouts (frame)
- #### Implementation Guidelines (React 19)
-
```typescript
- // Good Example - React 19: ref as a regular prop (no forwardRef needed)
- // packages/ui/src/components/button/button.tsx
- import { Slot } from "@radix-ui/react-slot";
-
- // Variant props can be typed however your styling solution requires
- export type ButtonVariant = "default" | "ghost" | "link";
- export type ButtonSize = "default" | "large" | "icon";
-
+ // React 19: ref as a regular prop, no forwardRef needed
export type ButtonProps = React.ComponentProps<"button"> & {
- variant?: ButtonVariant;
- size?: ButtonSize;
+ variant?: "default" | "ghost" | "link";
+ size?: "default" | "large" | "icon";
asChild?: boolean;
ref?: React.Ref<HTMLButtonElement>;
};
- // React 19: ref is passed as a regular prop - no forwardRef wrapper needed
- export function Button({
- variant = "default",
- size = "default",
- className,
- asChild = false,
- ref,
- ...props
- }: ButtonProps) {
- const Comp = asChild ? Slot : "button";
- // Apply styling via className - use any styling solution
- return (
- <Comp
- className={className}
- data-variant={variant}
- data-size={size}
- ref={ref}
- {...props}
- />
- );
+ export function Button({ variant = "default", size = "default", className, ref, ...props }: ButtonProps) {
+ return <button className={className} data-variant={variant} data-size={size} ref={ref} {...props} />;
}
```
- **Why good:** React 19 allows ref as a regular prop eliminating forwardRef boilerplate, named export enables tree-shaking and follows project conventions, className prop exposed for custom styling, data-attributes enable styling based on state/variants
+ **Why good:** ref as regular prop eliminates forwardRef boilerplate, className enables external styling, data-attributes enable CSS selectors for variants
- **When to use:** All reusable React components in the component library.
+ See [examples/core.md](examples/core.md) for complete component examples with good/bad comparisons.
---
### Pattern 2: Component Variant Props
- Components with multiple visual variants should expose type-safe variant props. The actual styling implementation is handled by your styling solution.
-
- #### When to Use Variant Props
-
- - Component has 2+ visual variants (default, ghost, outline)
- - Component has 2+ size variants (sm, md, lg)
- - Need type-safe variant props with autocomplete
-
- #### Implementation (React 19)
+ Components with 2+ visual dimensions (variant, size) should expose type-safe variant props via TypeScript unions. Use `data-*` attributes so any styling solution can target them.
```typescript
- // Good Example - Type-safe variant props with React 19 ref as prop
- const ANIMATION_DURATION_MS = 200;
-
export type AlertVariant = "info" | "warning" | "error" | "success";
- export type AlertSize = "sm" | "md" | "lg";
- export type AlertProps = React.ComponentProps<"div"> & {
- variant?: AlertVariant;
- size?: AlertSize;
- ref?: React.Ref<HTMLDivElement>;
- };
-
- // React 19: ref as a regular prop
- export function Alert({
- variant = "info",
- size = "md",
- className,
- style,
- ref,
- ...props
- }: AlertProps) {
- return (
- <div
- ref={ref}
- className={className}
- data-variant={variant}
- data-size={size}
- style={{ transition: `all ${ANIMATION_DURATION_MS}ms ease`, ...style }}
- {...props}
- />
- );
+ export function Alert({ variant = "info", className, ref, ...props }: AlertProps) {
+ return <div ref={ref} className={className} data-variant={variant} {...props} />;
}
```
- **Why good:** TypeScript union types provide autocomplete for variant values, data-attributes enable CSS styling based on variants, named constant for animation duration prevents magic numbers, React 19 ref as prop simplifies component definition
-
- **When not to use:** Simple components without variants (skip variant abstraction).
-
- ---
-
- ### Pattern 3: Icon Usage with lucide-react
-
- Use `lucide-react` for consistent, tree-shakeable icons. Icons inherit color from parent by default.
-
- #### Basic Icon Usage
-
- ```tsx
- // Good Example - Icon in button with accessibility
- import { ChevronDown } from "lucide-react";
- import { Button } from "@repo/ui/button";
-
- <Button size="icon" title="Expand details" aria-label="Expand details">
- <ChevronDown />
- </Button>;
- ```
-
- **Why good:** lucide-react provides tree-shakeable imports reducing bundle size, title attribute shows tooltip on hover, aria-label provides accessible name for screen readers, icon inherits color from button reducing CSS duplication
-
- #### Icon-Only Buttons with Accessibility
-
- ```typescript
- // Good Example - Accessible icon-only buttons
- import { CircleUserRound, CodeXml } from "lucide-react";
- import { Button } from "../../components/button/button";
-
- const GITHUB_URL = "https://github.com/username";
- const BLOG_URL = "https://blog.example.com";
-
- export const Socials = () => {
- return (
- <ul>
- <li>
- <Button
- size="icon"
- title="View GitHub profile"
- aria-label="View GitHub profile"
- onClick={() => window.open(GITHUB_URL, "_blank")}
- >
- <CodeXml />
- </Button>
- </li>
- <li>
- <Button
- size="icon"
- title="Visit blog"
- aria-label="Visit blog"
- onClick={() => window.open(BLOG_URL, "_blank")}
- >
- <CircleUserRound />
- </Button>
- </li>
- </ul>
- );
- };
- ```
+ **When not to use:** Components with a single visual style -- skip variant abstraction.
- **Why good:** both title and aria-label provide accessibility for different user needs, named constants for URLs prevent magic strings, title shows tooltip on hover, aria-label provides context for screen readers
+ See [examples/core.md](examples/core.md) for variant props with good/bad examples.
---
- ### Pattern 4: Event Handler Naming Conventions
-
- Use descriptive event handler names with `handle` prefix for internal handlers and `on` prefix for callback props.
-
- #### Naming Rules
+ ### Pattern 3: Event Handler Naming
- - `handle` prefix for internal handlers: `handleClick`, `handleSubmit`, `handleChange`
- - `on` prefix for callback props: `onClick`, `onSubmit`, `onChange`
- - Include the element or action: `handleNameChange`, `handlePriceBlur`
+ - `handle` prefix for internal handlers: `handleSubmit`, `handleNameChange`
+ - `on` prefix for callback props: `onClick`, `onSubmit`
- Type events explicitly: `FormEvent<HTMLFormElement>`, `ChangeEvent<HTMLInputElement>`
- #### Implementation
-
```typescript
- // Good Example - Descriptive event handler names
- import type { FormEvent, ChangeEvent } from "react";
-
- const MIN_PRICE = 0;
-
- function ProductForm() {
- const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
- e.preventDefault();
- // Submit logic
- };
-
- const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
- setName(e.target.value);
- };
-
- const handlePriceBlur = () => {
- if (price < MIN_PRICE) {
- setPrice(MIN_PRICE);
- }
- };
+ const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
+ e.preventDefault();
+ };
- return (
- <form onSubmit={handleSubmit}>
- <input onChange={handleNameChange} />
- <input onBlur={handlePriceBlur} />
- </form>
- );
- }
+ const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
+ setName(e.target.value);
+ };
```
- **Why good:** descriptive names make code self-documenting, explicit event types catch errors at compile time, named constant MIN_PRICE prevents magic number, handle prefix clearly identifies internal event handlers
+ See [examples/core.md](examples/core.md) for full event handler examples.
---
- ### Pattern 5: Custom Hooks
+ ### Pattern 4: Custom Hooks
Extract reusable logic into custom hooks following the `use` prefix convention.
- Key hooks covered:
-
- `usePagination` - Pagination state and navigation
- `useDebounce` - Debounce values for search inputs
- - `useLocalStorage` - Type-safe localStorage persistence
+ - `useLocalStorage` - Type-safe localStorage persistence with SSR safety
- See [examples/hooks.md](examples/hooks.md) for complete hook implementations.
+ See [examples/hooks.md](examples/hooks.md) for complete implementations.
---
- ### Pattern 6: Error Boundaries with Retry
+ ### Pattern 5: Error Boundaries with Retry
- Use Error Boundaries to catch React render errors and provide retry capability.
+ Error boundaries catch render errors and provide retry capability. Place them around feature sections, not just the root.
```typescript
- // Good Example - Error boundary with retry and logging
- import { Component, ErrorInfo, ReactNode } from "react";
- import { Button } from "./button";
-
+ // Key interface -- accepts custom fallback and error callback
interface Props {
children: ReactNode;
fallback?: (error: Error, reset: () => void) => ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
-
- interface State {
- hasError: boolean;
- error: Error | null;
- }
-
- export class ErrorBoundary extends Component<Props, State> {
- constructor(props: Props) {
- super(props);
- this.state = { hasError: false, error: null };
- }
-
- static getDerivedStateFromError(error: Error): State {
- return { hasError: true, error };
- }
-
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- console.error("Error boundary caught:", error, errorInfo);
- this.props.onError?.(error, errorInfo);
- }
-
- reset = () => {
- this.setState({ hasError: false, error: null });
- };
-
- render() {
- if (this.state.hasError && this.state.error) {
- if (this.props.fallback) {
- return this.props.fallback(this.state.error, this.reset);
- }
-
- return (
- <div role="alert" style={{ padding: "2rem", textAlign: "center" }}>
- <h2>Something went wrong</h2>
- <pre style={{ color: "red", marginTop: "1rem" }}>{this.state.error.message}</pre>
- <Button onClick={this.reset} style={{ marginTop: "1rem" }}>
- Try again
- </Button>
- </div>
- );
- }
-
- return this.props.children;
- }
- }
```
- **Why good:** catches render errors preventing full app crashes, retry capability allows recovery from transient errors, custom fallback prop enables branded error UI, onError callback enables error tracking integration
-
- **When to use:** Place error boundaries around feature sections, not just the root. Consider separate error boundaries for data fetching vs render errors.
+ **Limitation:** Error boundaries do not catch event handler errors, async errors, or SSR errors -- use try/catch for those.
- **When not to use:** Error boundaries don't catch event handler errors, async errors, or SSR errors - use try/catch for those.
+ See [examples/error-boundaries.md](examples/error-boundaries.md) for full implementation.
---
- ### Pattern 7: React 19 Actions with useActionState
+ ### Pattern 6: useActionState for Form Submissions
- Use `useActionState` for form submissions with automatic pending state and error handling.
+ Use `useActionState` for form submissions with automatic pending state and error handling. Replaces manual `useState` for loading/error.
```typescript
- // Good Example - Form with useActionState
import { useActionState } from "react";
async function updateProfile(prevState: string | null, formData: FormData) {
- const name = formData.get("name") as string;
-
try {
- await saveProfile({ name });
- return null; // Success - no error
- } catch (error) {
- return "Failed to save profile"; // Return error message
+ await saveProfile({ name: formData.get("name") as string });
+ return null;
+ } catch {
+ return "Failed to save profile";
}
}
export function ProfileForm() {
const [error, submitAction, isPending] = useActionState(updateProfile, null);
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? "Saving..." : "Save"}
</button>
{error && <p role="alert">{error}</p>}
</form>
);
}
```
- **Why good:** useActionState handles pending state automatically, error state managed by hook return value, form action works with progressive enhancement, no manual useState for loading/error
+ **Why good:** hook manages pending and error state automatically, form action works with progressive enhancement, no manual useState for loading/error
+ See [examples/react-19-hooks.md](examples/react-19-hooks.md) for extended examples with success state.
+
---
- ### Pattern 8: React 19 useFormStatus for Submit Buttons
+ ### Pattern 7: useFormStatus for Submit Buttons
- Use `useFormStatus` in child components to access parent form's pending state without prop drilling.
+ `useFormStatus` reads parent form's pending state without prop drilling. **Must** be called from a child component inside the `<form>`.
```typescript
- // Good Example - Submit button with useFormStatus
import { useFormStatus } from "react-dom";
- // Must be a child component inside the <form>, not the form component itself
function SubmitButton() {
const { pending } = useFormStatus();
-
return (
<button type="submit" disabled={pending}>
{pending ? "Submitting..." : "Submit"}
</button>
);
}
-
- export function ContactForm({ action }: { action: (formData: FormData) => Promise<void> }) {
- return (
- <form action={action}>
- <input type="text" name="message" />
- <SubmitButton /> {/* useFormStatus works here */}
- </form>
- );
- }
```
- **Why good:** useFormStatus reads parent form state without prop drilling, component is reusable across any form, pending state updates automatically during submission
+ **Gotcha:** Calling `useFormStatus` in the component that renders `<form>` returns `pending: false` always -- it must be a descendant component.
- **Gotcha:** `useFormStatus` must be called from a component rendered _inside_ the `<form>`, not in the same component that renders the form.
+ See [examples/react-19-hooks.md](examples/react-19-hooks.md) for reusable submit button patterns.
---
- ### Pattern 9: React 19 useOptimistic for Instant UI Updates
+ ### Pattern 8: useOptimistic for Instant UI Feedback
- Use `useOptimistic` to show immediate UI feedback while async operations complete.
+ Show immediate UI updates while async operations complete. State automatically reverts if the request fails.
```typescript
- // Good Example - Optimistic message sending
- import { useOptimistic, useRef, startTransition } from "react";
-
- type Message = { text: string; sending?: boolean };
-
- export function MessageThread({
- messages,
- sendMessage,
- }: {
- messages: Message[];
- sendMessage: (text: string) => Promise<void>;
- }) {
- const formRef = useRef<HTMLFormElement>(null);
- const [optimisticMessages, addOptimisticMessage] = useOptimistic(
- messages,
- (state, newText: string) => [
- ...state,
- { text: newText, sending: true },
- ]
- );
-
- async function formAction(formData: FormData) {
- const text = formData.get("message") as string;
- addOptimisticMessage(text);
- formRef.current?.reset();
+ import { useOptimistic, startTransition } from "react";
- startTransition(async () => {
- await sendMessage(text);
- });
- }
+ const [optimisticItems, addOptimistic] = useOptimistic(
+ items,
+ (state, update: Item) => [...state, { ...update, pending: true }],
+ );
- return (
- <>
- <ul>
- {optimisticMessages.map((msg, i) => (
- <li key={i}>
- {msg.text}
- {msg.sending && <span> (Sending...)</span>}
- </li>
- ))}
- </ul>
- <form action={formAction} ref={formRef}>
- <input type="text" name="message" />
- <button type="submit">Send</button>
- </form>
- </>
- );
- }
+ // In handler:
+ addOptimistic(newItem);
+ startTransition(async () => {
+ await saveItem(newItem);
+ });
```
- **Why good:** UI updates instantly without waiting for server response, sending indicator provides feedback, state automatically reverts if request fails
+ See [examples/react-19-hooks.md](examples/react-19-hooks.md) for todo list and chat examples.
---
- ### Pattern 10: React 19 use() Hook for Data and Context
+ ### Pattern 9: use() Hook for Promises and Context
- Use the `use()` API to read promises and context conditionally in render.
+ `use()` reads promises and context conditionally in render -- unlike `useContext`, it can be called after early returns.
```typescript
- // Good Example - Reading promises with use()
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
- const comments = use(commentsPromise);
-
- return (
- <ul>
- {comments.map((comment) => (
- <li key={comment.id}>{comment.text}</li>
- ))}
- </ul>
- );
- }
-
- export function Post({ post, commentsPromise }: Props) {
- return (
- <article>
- <h1>{post.title}</h1>
- <Suspense fallback={<p>Loading comments...</p>}>
- <Comments commentsPromise={commentsPromise} />
- </Suspense>
- </article>
- );
+ const comments = use(commentsPromise); // Suspends until resolved
+ return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
- ```
- ```typescript
- // Good Example - Reading context conditionally with use()
- import { use, createContext } from "react";
-
- const ThemeContext = createContext<string>("light");
-
- function Heading({ children }: { children: React.ReactNode | null }) {
- // use() can be called conditionally (unlike useContext)
- if (children === null) {
- return null;
- }
-
- const theme = use(ThemeContext);
- return <h1 data-theme={theme}>{children}</h1>;
- }
+ // Wrap in Suspense boundary
+ <Suspense fallback={<p>Loading...</p>}>
+ <Comments commentsPromise={fetchComments()} />
+ </Suspense>
```
- **Why good:** use() can be called conditionally unlike useContext, suspends automatically while promise resolves, integrates with Suspense boundaries for loading states
+ **Gotcha:** `use()` cannot be called in try-catch blocks -- use Error Boundaries for rejected promise handling.
- **Gotcha:** Cannot call `use()` in try-catch blocks - use Error Boundaries instead.
+ See [examples/react-19-hooks.md](examples/react-19-hooks.md) for conditional context reading.
---
- ### Pattern 11: React 19 Ref Cleanup Functions
+ ### Pattern 10: Ref Callback Cleanup Functions
- React 19 supports returning cleanup functions from ref callbacks.
+ React 19 ref callbacks can return cleanup functions, replacing the need for separate `useEffect` cleanup.
```typescript
- // Good Example - Ref with cleanup function
- import { useCallback } from "react";
-
function VideoPlayer({ src }: { src: string }) {
- const videoRef = useCallback((video: HTMLVideoElement | null) => {
- if (video === null) return;
-
- // Setup
- video.play();
-
- // Cleanup function - called when element unmounts
- return () => {
- video.pause();
- video.currentTime = 0;
- };
- }, []);
-
- return <video ref={videoRef} src={src} />;
+ return (
+ <video
+ ref={(video) => {
+ if (!video) return;
+ video.play();
+ return () => {
+ video.pause();
+ video.currentTime = 0;
+ };
+ }}
+ src={src}
+ />
+ );
}
```
- **Why good:** cleanup runs automatically when element unmounts, no need for separate useEffect for ref cleanup, pattern matches other React cleanup patterns
+ **Why good:** cleanup runs automatically on unmount, no separate useEffect needed, simpler than useRef + useEffect combination
+ **Note:** With ref cleanup functions, TypeScript rejects non-null/undefined return values from ref callbacks. The callback is no longer called with `null` on unmount -- the cleanup function handles that.
+
+ See [examples/react-19-hooks.md](examples/react-19-hooks.md) for IntersectionObserver cleanup example.
+
</patterns>
---
- <integration>
+ **Detailed Resources:**
- ## Integration Guide
+ - [examples/core.md](examples/core.md) - Component architecture, variants, event handlers
+ - [examples/hooks.md](examples/hooks.md) - usePagination, useDebounce, useLocalStorage
+ - [examples/react-19-hooks.md](examples/react-19-hooks.md) - useActionState, useFormStatus, useOptimistic, use(), ref cleanup
+ - [examples/error-boundaries.md](examples/error-boundaries.md) - Error boundary implementation and recovery
+ - [reference.md](reference.md) - Decision frameworks, anti-patterns, checklists
- **React is styling and state-agnostic.** Components should expose `className` prop for styling flexibility and use `useState` for component-local state.
+ ---
- **Works with:**
+ <red_flags>
- - **Radix UI**: Primitives like `Slot` for polymorphic components
- - **lucide-react**: Icon library for consistent iconography
- - Any CSS solution via `className` prop
- - Any state management solution via props or hooks
+ ## RED FLAGS
- **Component State Guidance:**
+ **High Priority Issues:**
- - Use `useState` for UI-only state local to a component
- - Use `useReducer` for complex local state with multiple sub-values
- - External state management decisions are separate from React component architecture
+ - Using `forwardRef` in React 19 -- deprecated, pass `ref` as a regular prop
+ - Calling `useFormStatus` in the component that renders `<form>` -- will always return `pending: false`
+ - Using `useState` for form loading/error when `useActionState` exists -- unnecessary boilerplate
+ - Not exposing `className` prop on reusable components -- prevents external styling
+ - Calling `use()` inside try-catch blocks -- use Error Boundaries instead
- </integration>
+ **Medium Priority Issues:**
+ - Adding variant abstractions for components without multiple variant dimensions
+ - Using `useCallback` on every handler regardless of child memoization (premature optimization)
+ - Wrapping ref callbacks in `useCallback` when the React Compiler handles memoization automatically
+ - Generic event handler names (`click`, `change`) instead of descriptive names (`handleNameChange`)
+
+ **Gotchas & Edge Cases:**
+
+ - Ref cleanup functions: TypeScript rejects non-null/undefined returns from ref callbacks -- use the cleanup pattern explicitly
+ - `useOptimistic` state reverts automatically on failure -- no manual rollback needed
+ - `use()` can be called conditionally (after early returns), unlike `useContext`
+ - Error boundaries do not catch event handler errors, async errors, or SSR errors
+ - `useCallback` without memoized children adds overhead without benefit
+ - SSR requires `typeof window !== "undefined"` before accessing browser APIs
+
+ </red_flags>
+
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md**
**(You MUST pass `ref` as a regular prop in React 19 - `forwardRef` is deprecated)**
**(You MUST expose `className` prop on ALL reusable components for customization)**
- **(You MUST use named constants for ALL numeric values - NO magic numbers)**
-
- **(You MUST use named exports - NO default exports in component libraries)**
-
**(You MUST use `useActionState` for form submissions with pending/error state)**
- **Failure to follow these rules will break component composition, prevent tree-shaking, and reduce code maintainability.**
+ **(You MUST call `useFormStatus` from a child component inside `<form>`, NOT in the component that renders the form)**
+
+ **Failure to follow these rules will break component composition, form state management, and styling flexibility.**
</critical_reminders>