react
Skill1 starsChanged 3 months agoMITView on GitHub
React + TypeScript frontend patterns. ALWAYS load for .tsx/.jsx files, React components, Vite projects, TanStack Query, CSS Modules, or any React-related task. Trigger vocabulary includes React Router, Vitest, Testing Library, React Hook Form, Zod, frontend components, hooks — even when the user doesn't say "React" explicitly.
- Installs packages
48 other projects use a version of this.
Adopted by
Most recently taken up by
- atinder-harika/reel-reactortheir own version, first seen Nov 8, 2026
- LuisSambrano/antigravity-configtheir own version, first seen Nov 8, 2026
- compozy/compozytheir own version, first seen Nov 7, 2026
- lobehub/lobehubtheir own version, first seen Nov 5, 2026
- hmj1026/dhpktheir own version, first seen Nov 1, 2026
- gingerminds/claude-skillstheir own version, first seen Nov 1, 2026
Used with
Projects with it also use app-renderer-systems, refactoring-analysis and 8 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
pedronauckSkillapp-renderer-systems
Create, extend, or debug frontend domain modules organized under systems/. Covers adapters, Query hooks, local state, and public exports; excludes generic components and backend implementation.
Also in 3 projects that use this
pedronauckSkillrefactoring-analysis
Audit code quality and architectural health or plan refactoring using Fowler code smells and techniques; write prioritized findings to docs/_refacs/. Excludes formatting, performance, and security audits.
Also in 3 projects that use this
andyridsSkillexpress
Run the ICM express-change pipeline - a small change no spec has to move for, with the plan record left intact
Also in 4 projects that use this
azlekovSkilltailwindcss
This skill should be used when the user asks to "style with Tailwind", "add CSS", "configure theme", "use @theme", "add custom colors", "implement dark mode", "use container queries", "add responsive design", "use OKLCH colors", or discusses styling, theming, or visual design. Always use the latest Tailwind CSS version and modern patterns.
Also in 5 projects that use this
jander99Skilltypescript-advanced
Design, implement, refactor, and debug advanced TypeScript patterns including generics, utility types, mapped types, conditional types, type guards, discriminated unions, and strict mode configuration. Use when writing type-safe code, fixing type errors, or improving type inference.
Also in 3 projects that use this
rawvegSkilllaravel
Laravel v12 - The PHP Framework For Web Artisans
Also in 6 projects that use this
Other versions48 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 48
- lobehub/lobe-chat★ 84ksame name
- lobehub/lobehub★ 84ksame name
- vercel-labs/json-render★ 19ksame name
- compozy/compozy★ 2.9ksame name
- BetterTyped/react-zoom-pan-pinch★ 1.9ksame name
- openops-cloud/openops★ 1.1ksame name
- pedronauck/skills★ 649same name
- Mindrally/skills★ 275same name
- kid-sid/claude-spellbook★ 190same name
- magnus919/agent-skills★ 172same name
38 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository18 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- clickhouseSkill
- distributed-systemsSkill
- flink-javaSkill
- flink-pythonSkill
- flink-sqlSkill
- gitSkill
- grokSkill
- java-prod-skillSkill
- javaSkill
- mermaid-skillSkill
- openapi_docSkill
- postgresSkill
- python-numericsSkill
- pythonSkill
- pytorchSkill
- rustSkill
- sqlSkill
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 6 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 38 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 21
Delete aggressively — git preserves history
- Distinctive: line 60
Colocation rule: If it's used by one component, it lives next to that component.
- Distinctive: line 104
No default exports — named exports are greppable and refactor-safe
- Distinctive: line 190
All magic values come from CSS custom properties — no hardcoded colors, spacing, or font sizes in component CSS
- Very specific: line 241
Every entity gets a key factory.
- Distinctive: line 270
No state for values derivable from other state — compute inline
- Very specific: line 272
Keep the work plan out of comments — no phases, plan steps, ticket numbers, or words like "for now", "temporary", "later".
- Distinctive: line 327
Behavioral over interaction testing: Test what the user sees and does, not implementation details.
- Very specific: line 432
Before committing or completing a review, read references/checklist.md in this skill's directory for the full checklist.
- Very specific: line 434
All props interfaces have readonly — no accidental mutation
- Very specific: line 437
No derived state in useState — compute inline
“Common” is measured: kept by at least 6 in 10 of the 38 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Version Policy
React 19+, TypeScript 6+, Vite 8+. React Compiler 1.0 (stable) handles memoization automatically — no manual useMemo/useCallback needed. ref is a regular prop — no forwardRef. Verify feature availability when uncertain.
Philosophy
- Components are functions — pure transforms from props + state to UI
- Composition over configuration — small components that compose, pass
childreninstead of drilling props, no god components - Colocation — styles, tests, types live next to the component they serve
- Server state is not UI state — TanStack Query for remote data,
useStatefor UI - Shared client state (theme, session, cart): lift state first, then a small typed context at the nearest boundary — this skill's default is no global state library (Redux, zustand) without measured prop-drilling pain
- Type-driven development — if the type is wrong, the UI is wrong
- Zero-runtime styling — CSS Modules, no CSS-in-JS runtime overhead
- Render only what changed — stable references, correct dependency arrays, no wasted renders
- Explicit over magic — no HOC chains, no deep context nesting, obvious data flow
- Delete aggressively — git preserves history
- Measure before optimizing — React DevTools Profiler, not intuition
Project Structure
src/
├── main.tsx # Entry — React.createRoot, QueryClient, Router
├── app.tsx # Root layout + route definitions
├── routes/ # Page components (1:1 with routes)
│ ├── home.tsx
│ └── users/
│ ├── list.tsx
│ └── detail.tsx
├── components/ # Shared UI components
│ ├── button/
│ │ ├── button.tsx
│ │ ├── button.module.css
│ │ └── button.test.tsx
│ └── layout/
├── features/ # Feature-scoped modules (components + hooks + types)
│ └── orders/
│ ├── components/
│ ├── hooks/
│ │ └── use-orders.ts
│ ├── types.ts
│ └── index.ts # Public API barrel
├── hooks/ # Shared custom hooks
├── lib/ # Utilities, API client, result type
│ ├── api-client.ts
│ ├── result.ts
│ ├── invariant.ts # cheap runtime asserts
│ └── cn.ts # classnames utility
├── types/ # Shared types
└── styles/
├── reset.css
└── variables.css # CSS custom properties (design tokens)Colocation rule: If it's used by one component, it lives next to that component. If shared, lift to the nearest common ancestor directory.
Invariants: lib/invariant.ts — a cheap assert that throws in all builds, for impossible states: context used outside its provider, exhaustive-switch defaults.
TypeScript Strict Mode
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}noUncheckedIndexedAccess is the most impactful — array/object indexing returns T | undefined, forcing you to handle missing values.
Component Patterns
Function Components — Props as Interface
interface UserCardProps {
readonly user: User;
readonly onSelect: (id: string) => void;
}
function UserCard({ user, onSelect }: UserCardProps) {
return (
<article className={styles.card} onClick={() => onSelect(user.id)}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</article>
);
}Rules:
- Named function declarations, not
const Component = () => {}— better stack traces, hoisting - Props interface with
readonly— prevents accidental mutation - Destructure props in parameter — no
props.xdrilling - No default exports — named exports are greppable and refactor-safe
Discriminated Unions for Variant Props
type ButtonProps =
| { readonly variant: "primary"; readonly onClick: () => void }
| { readonly variant: "link"; readonly href: string }
| { readonly variant: "submit" };
function Button(props: ButtonProps) {
switch (props.variant) {
case "primary":
return <button onClick={props.onClick} className={styles.primary}>Click</button>;
case "link":
return <a href={props.href} className={styles.link}>Link</a>;
case "submit":
return <button type="submit" className={styles.submit}>Submit</button>;
}
}Compiler enforces exhaustiveness — add a variant, get errors everywhere it's unhandled.
Guard Clauses in Components
function UserProfile({ userId }: { readonly userId: string }) {
const { data: user, isPending, error } = useUser(userId);
if (isPending) return <Skeleton />;
if (error) return <ErrorDisplay error={error} />;
if (!user) return <NotFound />;
// Happy path — no nesting
return (
<article>
<h1>{user.name}</h1>
<p>{user.email}</p>
</article>
);
}Early returns for loading/error/empty states. Happy path is always the last, un-nested return. Use TanStack Query v5's isPending (no data yet) — isLoading is the narrower isPending && isFetching and stays false for disabled queries.
CSS Modules
Zero-runtime, scoped CSS. No abstraction — just CSS with automatic class name isolation.
/* button.module.css */
.root {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-weight: 500;
transition: background-color 0.15s;
}
.primary {
composes: root;
background-color: var(--color-primary);
color: var(--color-on-primary);
}
.primary:hover {
background-color: var(--color-primary-hover);
}import styles from "./button.module.css";
function Button({ variant = "primary", children }: ButtonProps) {
return <button className={styles[variant]}>{children}</button>;
}Design Tokens as CSS Custom Properties
At project init, copy assets/variables.css from this skill's directory to src/styles/variables.css (spacing scale, colors, typography, radii, shadows) and extend it in place.
Rules:
- All magic values come from CSS custom properties — no hardcoded colors, spacing, or font sizes in component CSS
composes:for CSS composition — not class name concatenation- One
.module.cssper component — colocated
Data Fetching (TanStack Query)
Server state management — caching, deduplication, background refetch, retry.
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
// Query keys — structured, type-safe
const userKeys = {
all: ["users"] as const,
lists: () => [...userKeys.all, "list"] as const,
list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, "detail"] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
// Custom hook — encapsulates query config
function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => apiClient.get<User>(`/users/${id}`),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
function useUsers(filters: UserFilters) {
return useQuery({
queryKey: userKeys.list(filters),
queryFn: () => apiClient.get<User[]>("/users", { params: filters }),
});
}
// Mutation with cache invalidation
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (input: CreateUserInput) => apiClient.post<User>("/users", input),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
});
}Query Key Factory Pattern
Every entity gets a key factory. Structure: [entity, scope, ...params]. This enables precise cache invalidation — invalidateQueries({ queryKey: userKeys.lists() }) invalidates all user lists but not individual user details.
API Client
Thin wrapper over fetch. When creating the API client, read references/api-client.md in this skill's directory for the canonical implementation with interceptors, error handling, and type-safe methods.
// Usage — clean, typed
const user = await apiClient.get<User>("/users/123");
const created = await apiClient.post<User>("/users", { name: "Alice" });Result Type for API Boundaries
For complex error handling flows where you need to pattern-match on errors (not just display them), read references/result.md in this skill's directory.
For most React components, TanStack Query's built-in { data, error, isPending } is sufficient — you don't need a custom Result type at the component level. Use Result in the API client layer when you need to distinguish error types for business logic.
Hooks
Custom Hooks — Extract Reusable Logic
Encapsulate repeated stateful logic behind a typed signature, e.g. function useDebounce<T>(value: T, delayMs: number): T (setTimeout + cleanup in useEffect).
Rules of Hooks + Performance
- Extract repeated
useState+useEffectcombos into custom hooks useMemo/useCallbackonly when measured — not by default- Dependency arrays must be exhaustive — no lint suppressions
- No state for values derivable from other state — compute inline
- Comments are for the person reading and reviewing the code: the public API, anything not obvious, and the reason behind the code as a plain sentence (no
WHY:label) - Keep the work plan out of comments — no phases, plan steps, ticket numbers, or words like "for now", "temporary", "later". They go stale and someone has to find and delete them. Unfinished work belongs in
plan.md - Any function that does real work gets a header comment above it: one line on what it is for, plus its big-O for compute and memory. Tiny obvious helpers are exempt. The big-O lives in the header, not on a line inside the body:
// Group orders by customer for the summary table. O(n) compute, O(n) memory. function groupByCustomer(orders: readonly Order[]): Map<string, Order[]> { /* ... */ } - Non-trivial data transforms (sorts, groupings, derived collections) carry these big-O comments per CLAUDE.md — JSX glue and one-liner filters are exempt
- Editing counts too: when you change a function, keep the comments already there, fix any your change made wrong, and add the missing header, big-O, and reason. Do this only for code you add or change, not for nearby code you did not touch
// BAD: derived state in useState
const [filteredItems, setFilteredItems] = useState<Item[]>([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]);
// GOOD: compute inline — no state, no effect, no extra render
const filteredItems = items.filter(i => i.active);Performance
- React Compiler handles memoization — do NOT write manual
useMemo,useCallback, orReact.memo. The compiler 1.0 auto-memoizes components, hooks, and expressions. Writing manual memoization on top of the compiler is redundant and adds noise. - Don't optimize prematurely — React is fast. Measure with DevTools Profiler first.
- Virtualize long lists —
@tanstack/react-virtualfor lists > 100 items - Code split routes —
React.lazy()+Suspensefor route-level splitting - Images — lazy loading (
loading="lazy"), properwidth/heightto prevent layout shift
// Route-level code splitting
const UserList = lazy(() => import("./routes/users/list"));
const UserDetail = lazy(() => import("./routes/users/detail"));
<Suspense fallback={<Skeleton />}>
<Routes>
<Route path="/users" element={<UserList />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</Suspense>Error Boundaries
The one sanctioned class component — React has no hook API for getDerivedStateFromError / componentDidCatch. Wrap route-level components. Don't wrap every small component — boundaries should be at meaningful recovery points.
Forms (Controlled + Validation)
For simple forms, controlled inputs with useState. For complex forms with validation, read references/forms.md in this skill's directory for React Hook Form patterns.
React 19 Actions stance: mutations go through TanStack Query useMutation (cache invalidation lives there), not useActionState. Form state is useState or React Hook Form per references/forms.md — do not mix in <form action> / form actions.
Testing (Vitest + Testing Library)
TDD: red → green → vacuous mutation check. The regression test lands before the fix.
Behavioral over interaction testing. Test what the user sees and does, not implementation details.
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
describe("UserCard", () => {
it("displays user name and email", () => {
render(<UserCard user={{ id: "1", name: "Alice", email: "a@b.com" }} onSelect={() => {}} />);
expect(screen.getByText("Alice")).toBeInTheDocument();
expect(screen.getByText("a@b.com")).toBeInTheDocument();
});
it("calls onSelect with user id when clicked", async () => {
const user = userEvent.setup();
const onSelect = vi.fn();
render(<UserCard user={{ id: "1", name: "Alice", email: "a@b.com" }} onSelect={onSelect} />);
await user.click(screen.getByRole("article"));
expect(onSelect).toHaveBeenCalledWith("1");
});
});Testing TanStack Query Hooks
Stub the API boundary — hook tests must never hit the network. Spy on the apiClient singleton (lighter than a mock server; reach for MSW only in full integration suites).
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
import { vi } from "vitest";
import { apiClient } from "@/lib/api-client";
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return function Wrapper({ children }: { readonly children: React.ReactNode }) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};
}
it("fetches user by id", async () => {
vi.spyOn(apiClient, "get").mockResolvedValue({ id: "123", name: "Alice", email: "a@b.com" });
const { result } = renderHook(() => useUser("123"), { wrapper: createWrapper() });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.name).toBe("Alice");
});Anti-Patterns
// BAD: Testing implementation details
expect(component.state.isOpen).toBe(true);
// BAD: Querying by class name or test ID when better queries exist
screen.getByTestId("submit-btn");
// GOOD: Query by role, label, or text — what the user sees
screen.getByRole("button", { name: "Submit" });
screen.getByLabelText("Email");
screen.getByText("Welcome back");Query priority: getByRole > getByLabelText > getByText > getByTestId (last resort).
Dependencies
Before adding a dependency, check if it's already provided transitively: npm ls <package>. Duplicate direct dependencies cause version conflicts and lock file churn.
# Core
npm create vite@latest project-name -- --template react-ts
npm i react-router @tanstack/react-query
# React Compiler (auto-memoization — replaces manual useMemo/useCallback)
npm i -D babel-plugin-react-compiler @rolldown/plugin-babel @babel/core @types/babel__core
# Dev
npm i -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event
npm i -D @biomejs/biome
npm i -D @tanstack/react-query-devtoolsReact Router v7 is a single package in declarative mode — import from "react-router" (react-router-dom is a legacy re-export shim, do not install it).
Enable React Compiler in vite.config.ts (plugin-react v6 removed the inline babel option — use @rolldown/plugin-babel with the exported preset):
import { defineConfig } from "vite";
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
import babel from "@rolldown/plugin-babel";
export default defineConfig({
plugins: [react(), babel({ presets: [reactCompilerPreset()] })],
});Before Committing (Self-Review)
Before committing or completing a review, read references/checklist.md in this skill's directory for the full checklist. Quick top-of-mind checks:
- All props interfaces have
readonly— no accidental mutation - No
any— useunknownand narrow, or fix the type - Guard clauses for loading/error/empty — happy path last
- No derived state in
useState— compute inline - CSS values from custom properties — no hardcoded colors/spacing
- TanStack Query keys use factory pattern — precise invalidation
- Tests query by role/label/text — not implementation details
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

