# Reactjs

> React rules - functional components, hooks (useState, useEffect, useContext, useReducer, useRef, useMemo, useCallback, useTransition, useDeferredValue, useActionState, useFormStatus, useOptimistic, use), React Router v7, React Hook Form, conditional rendering, lists and keys, Context API, performance optimization, code splitting, Server Components, reconciliation, Virtual DOM, controlled vs uncontrolled components

- Skill: `14bryanespinoza/reactjs` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/reactjs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/reactjs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/reactjs

---


# React — Rules and Conventions

---

## 1. Philosophy

1. **Functional components only** — No class components. Hooks for all state/logic.
2. **Server Components by default** — Use `'use client'` only when needed (interactivity, hooks, browser APIs).
3. **TypeScript strict** — All components typed. Props interfaces exported.
4. **Composition over inheritance** — Build UI from small, composable components.
5. **Performance conscious** — Memoize only when measured. Trust the compiler (React 19+).

---

## 2. Version Baseline

| Technology      | Version       |
| --------------- | ------------- |
| React           | 18.3+ (19 RC) |
| React DOM       | 18.3+         |
| React Router    | 7.0+          |
| React Hook Form | 7.51+         |
| TypeScript      | 5.4+          |
| Node.js         | 22+           |

---

## 3. Setup & Project Structure

### Vite (SPA)

```bash
pnpm create vite@latest my-app -- --template react-ts
```

```text
src/
├── app/              # App shell, providers
├── components/       # Shared UI components
├── features/         # Feature-specific components + logic
├── hooks/            # Shared custom hooks
├── lib/              # Utilities, API clients
├── routes/           # Route components (lazy loaded)
├── styles/           # Global CSS, Tailwind entry
├── main.tsx          # Entry point
└── vite-env.d.ts
```

### Next.js (App Router)

```bash
pnpm create next-app@latest my-app --typescript --tailwind --eslint
```

```text
src/
├── app/              # Routes + Server Components
├── components/       # Client components ('use client')
├── lib/              # Utilities, DB, auth
├── hooks/            # Shared hooks
└── types/            # Global types
```

---

## 4. Functional Components

```tsx
// Component with props interface
interface ButtonProps {
  variant: "primary" | "secondary";
  onClick: () => void;
  children: React.ReactNode;
  disabled?: boolean;
}

export function Button({ variant, onClick, children, disabled }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}
```

### Rules

- **Named exports** — `export function Component()`
- **Props interface** — co-located, exported
- **`children` typed** — `React.ReactNode`
- **Optional props** — `?` with default in destructuring

---

## 5. Hooks — Rules & Closures

### Rules of Hooks

1. **Top level only** — no loops, conditions, nested functions
2. **Only in React functions** — components, custom hooks
3. **Name with `use` prefix** — `useCustomHook`

### Closure pitfalls

```tsx
// ❌ Stale closure
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setCount(count + 1), 1000);
    return () => clearInterval(id);
  }, []); // Missing count dependency
}

// ✅ Functional update
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setCount((c) => c + 1), 1000);
    return () => clearInterval(id);
  }, []); // No deps needed
}
```

---

## 6. Essential Hooks Patterns

### State

```tsx
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

// Lazy init (expensive computation)
const [data, setData] = useState(() => expensiveInitial());
```

### Side Effects

```tsx
// Mount only
useEffect(() => {
  const subscription = api.subscribe();
  return () => subscription.unsubscribe();
}, []);

// With deps
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

// Cleanup sync (React 18+)
useEffect(() => {
  return () => {
    /* cleanup */
  };
}, [dep]);
```

### Context

```tsx
// ThemeContext.tsx
const ThemeContext = createContext<ThemeContextType>(defaultValue);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<"light" | "dark">("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Usage
const { theme, setTheme } = useContext(ThemeContext);
```

### Reducer (complex state)

```tsx
interface State {
  status: "idle" | "loading" | "success" | "error";
  data: Data[];
}
type Action =
  | { type: "FETCH_START" }
  | { type: "FETCH_SUCCESS"; payload: Data[] }
  | { type: "FETCH_ERROR" };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "FETCH_START":
      return { ...state, status: "loading" };
    case "FETCH_SUCCESS":
      return { status: "success", data: action.payload };
    case "FETCH_ERROR":
      return { status: "error", data: [] };
  }
}

const [state, dispatch] = useReducer(reducer, initialState);
```

### Refs

```tsx
// DOM ref
const inputRef = useRef<HTMLInputElement>(null)
<input ref={inputRef} />

// Mutable value (no re-render)
const renderCount = useRef(0)
renderCount.current++
```

### Memoization

```tsx
// Referential equality
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b])
const memoizedCallback = useCallback(() => doSomething(a), [a])

// Component memo
const MemoizedChild = memo(function Child({ a, b }) { ... })
```

### Transitions (React 18+)

```tsx
function SearchResults({ query }: { query: string }) {
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);

  function handleChange(e: ChangeEvent<HTMLInputElement>) {
    const value = e.target.value;
    startTransition(() => {
      setResults(search(value)); // Urgent: input update
    });
  }
  // ...
}
```

### Deferred Value (React 18+)

```tsx
function SearchResults({ query }: { query: string }) {
  const deferredQuery = useDeferredValue(query);
  const results = useMemo(() => search(deferredQuery), [deferredQuery]);
  // ...
}
```

### Optimistic Updates (React 19+)

```tsx
function LikeButton({ postId }: { postId: string }) {
  const [likes, setLikes] = useState(0);
  const [optimistic, setOptimistic] = useOptimistic(
    likes,
    (state, action: "like" | "unlike") =>
      action === "like" ? state + 1 : state - 1,
  );

  async function handleClick() {
    setOptimistic("like");
    try {
      await api.like(postId);
      setLikes((l) => l + 1);
    } catch {
      setOptimistic("unlike"); // Rollback
    }
  }
  return <button onClick={handleClick}>{optimistic} ❤️</button>;
}
```

### Form Status / Action State (React 19+)

```tsx
function LoginForm() {
  const { pending, data, method, action } = useFormStatus();
  // or useActionState for server actions
}
```

---

## 7. Conditional Rendering

```tsx
// Ternary
{
  isLoggedIn ? <Dashboard /> : <Login />;
}

// Short-circuit
{
  isLoading && <Spinner />;
}

// Nullish coalescing
{
  data?.items ?? [];
}

// Early return
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return <Content data={data} />;
```

---

## 8. Lists & Keys

```tsx
// ✅ Stable, unique ID
{
  items.map((item) => <Item key={item.id} {...item} />);
}

// ❌ Index as key (breaks with reorder)
{
  items.map((item, i) => <Item key={i} {...item} />);
}

// ❌ Random/non-unique
{
  items.map((item) => <Item key={Math.random()} {...item} />);
}
```

---

## 9. Controlled vs Uncontrolled

```tsx
// Controlled (React owns state)
function ControlledInput() {
  const [value, setValue] = useState("");
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

// Uncontrolled (DOM owns state)
function UncontrolledInput() {
  const ref = useRef<HTMLInputElement>(null);
  return <input ref={ref} defaultValue="initial" />;
}

// Use controlled for: forms, validation, dependent fields
// Use uncontrolled for: simple inputs, file inputs, integration with non-React
```

---

## 10. Context API

```tsx
// Create context with undefined default (forces provider)
const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }) {
  const [user, setUser] = useState<User | null>(null);
  const login = async (creds) => {
    /* ... */
  };
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Hook for consumption
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error("useAuth must be used within AuthProvider");
  return context;
}
```

### Rules Context API

- **Split contexts** — separate rarely-changing from frequently-changing
- **Memoize provider value** — `useMemo` for object values
- **Custom hook for consumption** — enforces provider usage

---

## 11. Forms (React Hook Form)

```tsx
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

type FormData = z.infer<typeof schema>;

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
    defaultValues: { email: "", password: "" },
  });

  const onSubmit = async (data: FormData) => {
    await api.login(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} type="email" placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register("password")} type="password" placeholder="Password" />
      {errors.password && <span>{errors.password.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Loading..." : "Login"}
      </button>
    </form>
  );
}
```

---

## 12. React Router v7

```tsx
// routes.tsx
import { createBrowserRouter } from "react-router";

export const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> },
      { path: "dashboard", element: <Dashboard /> },
      { path: "settings", element: <Settings /> },
    ],
  },
  { path: "*", element: <NotFound /> },
]);
```

```tsx
// main.tsx
import { RouterProvider } from "react-router";
import { router } from "./routes";

createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />,
);
```

### Data loading (loaders)

```tsx
const router = createBrowserRouter([
  {
    path: "/posts/:id",
    element: <PostDetail />,
    loader: async ({ params }) => {
      const post = await api.getPost(params.id);
      return post;
    },
  },
]);

// In component
function PostDetail() {
  const post = useLoaderData<Post>();
  // ...
}
```

### Actions (mutations)

```tsx
<Form method="post" action="/posts">
  <input name="title" />
  <button type="submit">Create</button>
</Form>;

// Server action
export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  await api.createPost(Object.fromEntries(formData));
  return redirect("/posts");
}
```

---

## 13. Performance

> **Full performance rules**: see `performance` skill.

### Key patterns

```tsx
// Memoize component
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  return <ComplexView data={data} />;
});

// Memoize callbacks passed to memoized children
function Parent() {
  const handleClick = useCallback(() => {
    /* ... */
  }, [dep]);
  return <ExpensiveComponent onClick={handleClick} />;
}

// Virtualize long lists
import { FixedSizeList } from "react-window";

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => <div style={style}>{items[index].name}</div>}
    </FixedSizeList>
  );
}
```

---

## 14. Code Splitting

```tsx
// Route-level (React Router v7)
const router = createBrowserRouter([
  {
    path: "/admin",
    lazy: () =>
      import("./routes/admin").then((m) => ({ default: m.AdminLayout })),
  },
]);

// Component-level
const HeavyChart = lazy(() => import("./HeavyChart"));

function Dashboard() {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <HeavyChart />
    </Suspense>
  );
}
```

> **Build config**: see `vite` and `esbuild` skills.

---

## 15. Server Components (Next.js)

> **Full Next.js patterns**: see `nextjs` skill.

```tsx
// app/posts/page.tsx (Server Component by default)
async function PostsPage() {
  const posts = await db.posts.findMany(); // Direct DB access
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// Client Component (interactivity)
("use client");

export function LikeButton({ postId }) {
  const [likes, setLikes] = useState(0);
  // ...
}
```

### Rules Server Components

- **Default to Server Components** — data fetching, static content
- **`'use client'` only when needed** — hooks, browser APIs, interactivity
- **Pass data as props** — Server → Client
- **Actions for mutations** — Server Actions

---

## 16. Error Handling

```tsx
// Error Boundary (class component required)
class ErrorBoundary extends Component<
  { children: React.ReactNode },
  { error: Error | null }
> {
  state = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    logError(error, info);
  }

  render() {
    if (this.state.error) return <ErrorFallback error={this.state.error} />;
    return this.props.children;
  }
}

// Usage
<ErrorBoundary fallback={<ErrorFallback />}>
  <App />
</ErrorBoundary>;
```

### React 19+ (useActionState for forms)

```tsx
function Form() {
  const [state, action] = useActionState(
    async (prev, formData) => {
      try {
        await api.submit(formData);
        return { success: true };
      } catch (e) {
        return { error: e.message };
      }
    },
    { success: false, error: null },
  );

  return <form action={action}>...</form>;
}
```

---

## 17. Methodology

Before using ANY React pattern not documented in this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` + `context7_query-docs` for React, React Router, React Hook Form.
2. **Official docs**: react.dev, reactrouter.com, react-hook-form.com — verify current APIs.
3. **Project config**: `package.json`, `tsconfig.json`, router config — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.

---

## 18. Prohibitions

- ❌ Do not use class components — functional + hooks only
- ❌ Do not mutate state directly — `setState(prev => ...)`
- ❌ Do not use `useEffect` for data fetching without cleanup
- ❌ Do not pass inline objects to memoized children
  — `style={{}}`, `onClick={() => {}}`
- ❌ Do not use `useMemo`/`useCallback` everywhere — only when measured
- ❌ Do not skip `key` in lists — use stable IDs
- ❌ Do not use context for high-frequency updates — splits contexts
- ❌ Do not use `'use client'` by default — Server Components first
- ❌ Do not call hooks conditionally — Rules of Hooks

---

## 19. References

> **Note:** For HTML conventions (JSX semantics), see [HTML](../html/SKILL.md)
> **Note:** For CSS conventions (styling), see [CSS](../css/SKILL.md)
> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For TypeScript rules, see [TypeScript](../typescript/SKILL.md)
> **Note:** For Performance rules, see [Performance](../performance/SKILL.md)
> **Note:** For Next.js patterns, see [Next.js](../nextjs/SKILL.md)
> **Note:** For Vite config, see [Vite](../vite/SKILL.md)
> **Note:** For Tailwind CSS, see [Tailwind CSS](../tailwindcss/SKILL.md)
> **Note:** For Sass, see [Sass](../sass/SKILL.md)
> **Note:** For Component Design, see [Component Design](../component-design/SKILL.md)

---

Last updated: 2026-08

