{"id":"6eeed6c3-20d5-47ad-a9d5-d5d6ae01188d","shortId":"VFEJ9Y","kind":"skill","title":"typescript-react-patterns","tagline":"Production-grade TypeScript reference for React & Next.js frontend development. Covers type narrowing, component Props, generic hooks, discriminated unions, as const, satisfies, Zod validation, TanStack Query, server/client boundaries, forms, state management, performance, access","description":"# TypeScript for React & Next.js — Agent Skill\n\nA structured reference for AI coding agents assisting frontend engineers with TypeScript, React, and Next.js in production environments.\n\n---\n\n## Agent Behavior Rules\n\n### Before answering, always verify:\n\n1. **Server or client?** Server Components, Server Actions, and Route Handlers have different type constraints than `\"use client\"` components.\n2. **Runtime validation needed?** Static types do NOT validate API responses, URL params, form data, or localStorage. Data crossing a trust boundary requires Zod or equivalent.\n3. **App Router or Pages Router?** Patterns differ significantly. If unclear, ask.\n4. **TypeScript version?** `satisfies` requires 5.0+. Check before suggesting version-dependent features.\n5. **Next.js version?** `params` is a `Promise` in 15+. Caching model changed in 16+.\n\n### Assumptions the agent must NOT make:\n\n- That API responses match their TypeScript types at runtime\n- That `searchParams` values are the expected type (they are always `string | string[] | undefined`)\n- That `any` in existing code is intentional\n- That a type assertion (`as`) is justified without checking context\n- That server-only imports are safe in client components\n- That `useEffect` dependencies in existing code are correct\n\n### When uncertain:\n\n- State tradeoffs explicitly rather than picking one approach silently\n- Mark unstable or version-dependent patterns as such\n- Distinguish: **[HARD RULE]** (violating causes bugs) / **[DEFAULT]** (override with reason) / **[SITUATIONAL]** (depends on context)\n\n---\n\n## Decision Guide\n\n### Quick: What pattern should I use?\n\n| Situation | Start here |\n|-----------|-----------|\n| Typing component Props, children, events, refs | → `react-typescript-patterns.md` |\n| Narrowing unions, `unknown`, type guards, utility types | → `typescript-core.md` |\n| Next.js params, searchParams, Server Actions, RSC boundary | → `nextjs-typescript.md` |\n| Discriminated unions, conditional props, compound components | → `component-patterns.md` |\n| API responses, fetch typing, TanStack Query, caching | → `data-fetching-and-api-types.md` |\n| Form state, validation, controlled vs uncontrolled | → `forms-and-validation.md` |\n| Local state vs context vs server state vs Zustand | → `state-management.md` |\n| Re-renders, memoization, accessibility | → `performance-and-accessibility.md` |\n| Type errors, hydration, stale state, effect bugs | → `debugging-checklists.md` (hub) + `playbooks/` |\n| PR review, risk vs preference, architecture smells | → `code-review-rules.md` |\n| Common mistakes, cargo-cult patterns | → `anti-patterns.md` |\n\n### Flowchart: Is this data safe to use?\n\n```\nData comes from...\n├─ Inside the app (useState, useReducer, computed)\n│  → Static typing is sufficient. No runtime validation needed.\n│\n├─ Outside the app (API, URL, FormData, localStorage, postMessage)\n│  → [HARD RULE] Validate at runtime. Use Zod or equivalent.\n│  │\n│  ├─ API response    → schema.parse(await res.json())\n│  ├─ URL params      → schema.parse(searchParams)\n│  ├─ FormData        → schema.safeParse({ field: formData.get('field') })\n│  ├─ localStorage    → schema.safeParse(JSON.parse(stored))\n│  └─ postMessage     → schema.safeParse(event.data)\n│\n└─ Third-party library callback\n   → Check library types. Add runtime guard if types seem wrong.\n```\n\n### Flowchart: Where should this state live?\n\n```\nIs this data from a server/API?\n├─ Yes → TanStack Query (NOT useState). See data-fetching-and-api-types.md\n│\n└─ No → Is it shareable via URL? (filters, page, sort)\n   ├─ Yes → searchParams or nuqs. See state-management.md\n   │\n   └─ No → How many components need it?\n      ├─ 1 component → useState or useReducer\n      ├─ 2-3 in same tree → Lift state up (props)\n      └─ Many across trees → How often does it change?\n         ├─ Rarely (theme, locale, auth) → Context\n         └─ Often (cart, notifications) → Zustand with selectors\n```\n\n### Flowchart: Should I memoize this?\n\n```\nIs there a measured performance problem?\n├─ No → Don't memoize. Stop here.\n│\n└─ Yes → Can you restructure instead?\n   ├─ Yes → Move state down, extract components. See performance-and-accessibility.md\n   │\n   └─ No → What needs memoizing?\n      ├─ Expensive computation → useMemo (verify it's truly expensive)\n      ├─ Callback to memoized child → useCallback\n      └─ Component in a long list → React.memo (verify props are stable)\n```\n\n### Quick: hard rule vs default vs situational\n\n| Label | Meaning | Example |\n|-------|---------|---------|\n| **[HARD RULE]** | Violating causes bugs or security issues. No exceptions. | \"Validate API responses at runtime\" |\n| **[DEFAULT]** | Recommended unless you have a documented reason to deviate. | \"Use `interface` for Props\" |\n| **[SITUATIONAL]** | Depends on context. Both options are valid. Explain your choice. | \"Polymorphic components — only for design-system foundations\" |\n\n---\n\n## Code Generation Checklist\n\nBefore generating TypeScript/React/Next.js code:\n\n**Context**\n- [ ] Confirmed: server or client code?\n- [ ] Confirmed: App Router or Pages Router?\n- [ ] Confirmed: TypeScript strict mode enabled?\n\n**Type Safety**\n- [ ] No `any` — use `unknown` with validation or proper types\n- [ ] No `as` without documented justification\n- [ ] External data (API, URL, form, storage) validated at runtime\n- [ ] Props use `interface`, only truly optional fields have `?`\n\n**React**\n- [ ] `children` typed as `React.ReactNode`\n- [ ] Event handler Props expose values, not event objects\n- [ ] Effects have stable dependencies and cleanup functions\n- [ ] `\"use client\"` only where needed, as deep as possible\n- [ ] No server data duplicated into `useState`\n\n**Next.js (15+)**\n- [ ] `params` and `searchParams` awaited\n- [ ] Server Actions validate FormData with Zod\n- [ ] Sensitive code protected with `import 'server-only'`\n- [ ] Cross-boundary Props are JSON-serializable (no functions, Dates, Maps)\n\n**Accessibility**\n- [ ] Form inputs have associated labels\n- [ ] Error messages use `role=\"alert\"`\n- [ ] Interactive elements are keyboard-accessible\n\n---\n\n## Code Review Checklist\n\n### Flag as risk (likely bug or maintenance problem)\n\n- `any` without documented reason\n- `as` on external data without validation\n- `!` non-null assertion without prior guard\n- `useEffect` with object/array dependencies (likely unstable)\n- Missing `useEffect` cleanup\n- Server data copied into `useState`\n- `\"use client\"` at page/layout level\n- Functions passed across server/client boundary\n- `params`/`searchParams` not awaited (Next.js 15+)\n- Server Action without FormData validation\n\n### Flag as preference (mention, don't block)\n\n- `type` vs `interface` for object shapes\n- Handler naming convention\n- File/folder organization style\n- Import ordering\n\n---\n\n## File Index\n\n| File | Scope |\n|------|-------|\n| `typescript-core.md` | Narrowing, unions, generics, utility types, inference, `unknown` vs `any`, `as const`, `satisfies` |\n| `react-typescript-patterns.md` | Props, children, events, hooks, context, forwardRef |\n| `nextjs-typescript.md` | App Router types, params, searchParams, Server Actions, RSC boundaries, metadata |\n| `component-patterns.md` | Discriminated union Props, compound components, controlled/uncontrolled, polymorphic |\n| `data-fetching-and-api-types.md` | Fetch typing, Zod validation, TanStack Query, safe response handling |\n| `forms-and-validation.md` | Form state, Zod, react-hook-form, Server Actions, progressive enhancement |\n| `state-management.md` | Local state, Context, Zustand, TanStack Query, URL state, decision matrix |\n| `performance-and-accessibility.md` | Memoization tradeoffs, effect stability, semantic HTML, ARIA patterns |\n| `debugging-checklists.md` | Quick diagnosis router, serialization issues, null access, re-render errors |\n| `code-review-rules.md` | Risk vs preference, architecture smells, review comment templates |\n| `anti-patterns.md` | 12 common mistakes with root causes and fixes |\n\n### `playbooks/` — Step-by-step debugging guides (consult when diagnosing specific bugs)\n\n| File | Scope |\n|------|-------|\n| `type-error-debugging.md` | Systematic type error resolution with React/Next.js-specific errors |\n| `hydration-issues.md` | SSR/CSR mismatch diagnosis flowchart and fix patterns |\n| `effect-dependency-bugs.md` | Infinite loops, stale closures, missing cleanups, real-world debounce example |","tags":["typescript","react","patterns","leejpsd","agent-skills","claude-code","claude-code-skill","claude-code-skills","codex-cli","cursor","nextjs","openclaw"],"capabilities":["skill","source-leejpsd","skill-typescript-react-patterns","topic-agent-skills","topic-claude-code","topic-claude-code-skill","topic-claude-code-skills","topic-codex-cli","topic-cursor","topic-nextjs","topic-openclaw","topic-react","topic-skill-md","topic-skillsmp","topic-typescript"],"categories":["typescript-react-patterns"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/leejpsd/typescript-react-patterns","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add leejpsd/typescript-react-patterns","source_repo":"https://github.com/leejpsd/typescript-react-patterns","install_from":"skills.sh"}},"qualityScore":"0.457","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 15 github stars · SKILL.md body (8,377 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:06:32.060Z","embedding":null,"createdAt":"2026-04-23T13:04:20.471Z","updatedAt":"2026-05-18T19:06:32.060Z","lastSeenAt":"2026-05-18T19:06:32.060Z","tsv":"'-3':470 '1':69,464 '12':944 '15':147,705,810 '16':152 '2':88,469 '3':114 '4':126 '5':139 '5.0':131 'access':37,320,736,752,929 'across':479,802 'action':76,280,711,812,868,899 'add':417 'agent':42,50,62,155 'ai':48 'alert':746 'alway':67,177 'answer':66 'anti-patterns.md':346,943 'api':97,160,291,374,388,575,654 'app':115,359,373,626,862 'approach':225 'architectur':337,938 'aria':920 'ask':125 'assert':191,777 'assist':51 'associ':740 'assumpt':153 'auth':489 'await':391,709,808 'behavior':63 'block':822 'boundari':32,109,282,726,804,870 'bug':241,328,568,760,963 'cach':148,297 'callback':413,539 'cargo':343 'cargo-cult':342 'cart':492 'caus':240,567,949 'chang':150,485 'check':132,196,414 'checklist':614,755 'child':542 'children':264,670,856 'choic':603 'cleanup':687,789,988 'client':72,86,206,623,690,796 'closur':986 'code':49,185,213,612,618,624,717,753 'code-review-rules.md':339,934 'come':355 'comment':941 'common':340,945 'compon':18,74,87,207,262,289,461,465,524,544,605,877 'component-patterns.md':290,872 'compound':288,876 'comput':362,532 'condit':286 'confirm':620,625,631 'const':25,852 'constraint':83 'consult':959 'context':197,249,309,490,596,619,859,905 'control':302 'controlled/uncontrolled':878 'convent':831 'copi':792 'correct':215 'cover':15 'cross':106,725 'cross-boundari':724 'cult':344 'data':102,105,350,354,432,653,700,771,791 'data-fetching-and-api-types.md':298,442,880 'date':734 'debounc':992 'debug':957 'debugging-checklists.md':329,922 'decis':250,911 'deep':695 'default':242,558,579 'depend':137,210,232,247,594,685,784 'design':609 'design-system':608 'develop':14 'deviat':588 'diagnos':961 'diagnosi':924,977 'differ':81,121 'discrimin':22,284,873 'distinguish':236 'document':585,650,766 'duplic':701 'effect':327,682,916 'effect-dependency-bugs.md':982 'element':748 'enabl':635 'engin':53 'enhanc':901 'environ':61 'equival':113,387 'error':323,742,933,969,973 'event':265,674,680,857 'event.data':408 'exampl':563,993 'except':573 'exist':184,212 'expect':173 'expens':531,538 'explain':601 'explicit':220 'expos':677 'extern':652,770 'extract':523 'featur':138 'fetch':293,881 'field':399,401,667 'file':837,839,964 'file/folder':832 'filter':449 'fix':951,980 'flag':756,816 'flowchart':347,424,497,978 'form':33,101,299,656,737,891,897 'formdata':376,397,713,814 'formdata.get':400 'forms-and-validation.md':305,890 'forwardref':860 'foundat':611 'frontend':13,52 'function':688,733,800 'generat':613,616 'generic':20,844 'grade':7 'guard':272,419,780 'guid':251,958 'handl':889 'handler':79,675,829 'hard':237,379,555,564 'hook':21,858,896 'html':919 'hub':330 'hydrat':324 'hydration-issues.md':974 'import':202,720,835 'index':838 'infer':847 'infinit':983 'input':738 'insid':357 'instead':518 'intent':187 'interact':747 'interfac':590,663,825 'issu':571,927 'json':730 'json-serializ':729 'json.parse':404 'justif':651 'justifi':194 'keyboard':751 'keyboard-access':750 'label':561,741 'level':799 'librari':412,415 'lift':474 'like':759,785 'list':548 'live':429 'local':306,488,903 'localstorag':104,377,402 'long':547 'loop':984 'mainten':762 'make':158 'manag':35 'mani':460,478 'map':735 'mark':227 'match':162 'matrix':912 'mean':562 'measur':505 'memoiz':319,500,511,530,541,914 'mention':819 'messag':743 'metadata':871 'mismatch':976 'miss':787,987 'mistak':341,946 'mode':634 'model':149 'move':520 'must':156 'name':830 'narrow':17,268,842 'need':91,370,462,529,693 'next.js':12,41,58,140,276,704,809 'nextjs-typescript.md':283,861 'non':775 'non-nul':774 'notif':493 'null':776,928 'nuq':455 'object':681,827 'object/array':783 'often':482,491 'one':224 'option':598,666 'order':836 'organ':833 'outsid':371 'overrid':243 'page':118,450,629 'page/layout':798 'param':100,142,277,394,706,805,865 'parti':411 'pass':801 'pattern':4,120,233,254,345,921,981 'perform':36,506 'performance-and-accessibility.md':321,526,913 'pick':223 'playbook':331,952 'polymorph':604,879 'possibl':697 'postmessag':378,406 'pr':332 'prefer':336,818,937 'prior':779 'problem':507,763 'product':6,60 'production-grad':5 'progress':900 'promis':145 'prop':19,263,287,477,551,592,661,676,727,855,875 'proper':645 'protect':718 'queri':30,296,438,886,908 'quick':252,554,923 'rare':486 'rather':221 're':317,931 're-rend':316,930 'react':3,11,40,56,669,895 'react-hook-form':894 'react-typescript-patterns.md':267,854 'react.memo':549 'react.reactnode':673 'react/next.js-specific':972 'real':990 'real-world':989 'reason':245,586,767 'recommend':580 'ref':266 'refer':9,46 'render':318,932 'requir':110,130 'res.json':392 'resolut':970 'respons':98,161,292,389,576,888 'restructur':517 'review':333,754,940 'risk':334,758,935 'role':745 'root':948 'rout':78 'router':116,119,627,630,863,925 'rsc':281,869 'rule':64,238,380,556,565 'runtim':89,167,368,383,418,578,660 'safe':204,351,887 'safeti':637 'satisfi':26,129,853 'schema.parse':390,395 'schema.safeparse':398,403,407 'scope':840,965 'searchparam':169,278,396,453,708,806,866 'secur':570 'see':441,456,525 'seem':422 'selector':496 'semant':918 'sensit':716 'serial':926 'serializ':731 'server':70,73,75,200,279,311,621,699,710,722,790,811,867,898 'server-on':199,721 'server/api':435 'server/client':31,803 'shape':828 'shareabl':446 'signific':122 'silent':226 'situat':246,258,560,593 'skill':43 'skill-typescript-react-patterns' 'smell':338,939 'sort':451 'source-leejpsd' 'specif':962 'ssr/csr':975 'stabil':917 'stabl':553,684 'stale':325,985 'start':259 'state':34,218,300,307,312,326,428,475,521,892,904,910 'state-management.md':315,457,902 'static':92,363 'step':954,956 'step-by-step':953 'stop':512 'storag':657 'store':405 'strict':633 'string':178,179 'structur':45 'style':834 'suffici':366 'suggest':134 'system':610 'systemat':967 'tanstack':29,295,437,885,907 'templat':942 'theme':487 'third':410 'third-parti':409 'topic-agent-skills' 'topic-claude-code' 'topic-claude-code-skill' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-cursor' 'topic-nextjs' 'topic-openclaw' 'topic-react' 'topic-skill-md' 'topic-skillsmp' 'topic-typescript' 'tradeoff':219,915 'tree':473,480 'truli':537,665 'trust':108 'type':16,82,93,165,174,190,261,271,274,294,322,364,416,421,636,646,671,823,846,864,882,968 'type-error-debugging.md':966 'typescript':2,8,38,55,127,164,632 'typescript-core.md':275,841 'typescript-react-pattern':1 'typescript/react/next.js':617 'uncertain':217 'unclear':124 'uncontrol':304 'undefin':180 'union':23,269,285,843,874 'unknown':270,641,848 'unless':581 'unstabl':228,786 'url':99,375,393,448,655,909 'use':85,257,353,384,589,640,662,689,744,795 'usecallback':543 'useeffect':209,781,788 'usememo':533 'usereduc':361,468 'usest':360,440,466,703,794 'util':273,845 'valid':28,90,96,301,369,381,574,600,643,658,712,773,815,884 'valu':170,678 'verifi':68,534,550 'version':128,136,141,231 'version-depend':135,230 'via':447 'violat':239,566 'vs':303,308,310,313,335,557,559,824,849,936 'without':195,649,765,772,778,813 'world':991 'wrong':423 'yes':436,452,514,519 'zod':27,111,385,715,883,893 'zustand':314,494,906","prices":[{"id":"fc05484c-dd34-42d4-b336-396eedd67305","listingId":"6eeed6c3-20d5-47ad-a9d5-d5d6ae01188d","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"leejpsd","category":"typescript-react-patterns","install_from":"skills.sh"},"createdAt":"2026-04-23T13:04:20.471Z"}],"sources":[{"listingId":"6eeed6c3-20d5-47ad-a9d5-d5d6ae01188d","source":"github","sourceId":"leejpsd/typescript-react-patterns","sourceUrl":"https://github.com/leejpsd/typescript-react-patterns","isPrimary":false,"firstSeenAt":"2026-04-23T13:04:20.471Z","lastSeenAt":"2026-05-18T19:06:32.060Z"}],"details":{"listingId":"6eeed6c3-20d5-47ad-a9d5-d5d6ae01188d","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"leejpsd","slug":"typescript-react-patterns","github":{"repo":"leejpsd/typescript-react-patterns","stars":15,"topics":["agent-skills","claude-code","claude-code-skill","claude-code-skills","codex-cli","cursor","nextjs","openclaw","react","skill-md","skillsmp","typescript"],"license":"mit","html_url":"https://github.com/leejpsd/typescript-react-patterns","pushed_at":"2026-04-19T16:17:27Z","description":"Production-grade TypeScript patterns for React & Next.js — Agent Skill for Claude Code, Cursor, Codex. 17 modules, 4K+ lines covering typing, debugging, code review, and architecture decisions.","skill_md_sha":"e19dd755494667a0dd2629340305dd0431896585","skill_md_path":"SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/leejpsd/typescript-react-patterns"},"layout":"root","source":"github","category":"typescript-react-patterns","frontmatter":{"name":"typescript-react-patterns","description":"Production-grade TypeScript reference for React & Next.js frontend development. Covers type narrowing, component Props, generic hooks, discriminated unions, as const, satisfies, Zod validation, TanStack Query, server/client boundaries, forms, state management, performance, accessibility, debugging, and code review. Use when the user works with TypeScript in React or Next.js: type errors, Props design, generics, API typing, SSR/CSR boundaries, hydration issues, form validation, state management, performance, or code review. Also use for \"how should I type this?\", \"why does this type error happen?\", or any architectural decision involving TypeScript in a frontend context."},"skills_sh_url":"https://skills.sh/leejpsd/typescript-react-patterns"},"updatedAt":"2026-05-18T19:06:32.060Z"}}