[{"data":1,"prerenderedAt":1334},["ShallowReactive",2],{"page-\u002Freact\u002F23-typescript-with-react":3},{"id":4,"title":5,"body":6,"description":1327,"extension":1328,"meta":1329,"navigation":46,"path":1330,"seo":1331,"stem":1332,"__hash__":1333},"content\u002Freact\u002F23-typescript-with-react.md","23 — TypeScript with React",{"type":7,"value":8,"toc":1315},"minimark",[9,13,18,195,199,354,358,532,536,670,674,815,819,935,939,1028,1032,1131,1135,1143,1170,1228,1232,1311],[10,11,5],"h1",{"id":12},"_23-typescript-with-react",[14,15,17],"h2",{"id":16},"typing-component-props","Typing Component Props",[19,20,23],"code-wrapper",{"filename":21,"language":22},"typing_props.tsx","typescript",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-tsx shiki shiki-themes github-light github-dark","import { type ReactNode, type PropsWithChildren } from 'react'\n\n\u002F\u002F BASIC: interface for props\ninterface ButtonProps {\n  label: string\n  variant?: 'primary' | 'secondary' | 'danger'  \u002F\u002F union = autocomplete + exhaustiveness\n  size?: 'sm' | 'md' | 'lg'\n  onClick: (event: React.MouseEvent\u003CHTMLButtonElement>) => void\n  disabled?: boolean\n  children?: ReactNode  \u002F\u002F anything renderable: string, element, array of elements\n}\n\nfunction Button({ label, variant = 'primary', size = 'md', onClick, disabled, children }: ButtonProps) {\n  return (\n    \u003Cbutton\n      className={`btn btn-${variant} btn-${size}`}\n      onClick={onClick}\n      disabled={disabled}\n    >\n      {label || children}\n    \u003C\u002Fbutton>\n  )\n}\n\n\u002F\u002F type vs interface: use `type` for unions and computed types, `interface` for\n\u002F\u002F object shapes that might be extended. For props, `interface` is conventional\n\u002F\u002F but `type` works identically. Prefer `type` if you don't need declaration merging.\n","tsx","",[31,32,33,41,48,54,60,66,72,78,84,90,96,102,107,113,119,125,131,137,143,149,155,161,167,172,177,183,189],"code",{"__ignoreMap":29},[34,35,38],"span",{"class":36,"line":37},"line",1,[34,39,40],{},"import { type ReactNode, type PropsWithChildren } from 'react'\n",[34,42,44],{"class":36,"line":43},2,[34,45,47],{"emptyLinePlaceholder":46},true,"\n",[34,49,51],{"class":36,"line":50},3,[34,52,53],{},"\u002F\u002F BASIC: interface for props\n",[34,55,57],{"class":36,"line":56},4,[34,58,59],{},"interface ButtonProps {\n",[34,61,63],{"class":36,"line":62},5,[34,64,65],{},"  label: string\n",[34,67,69],{"class":36,"line":68},6,[34,70,71],{},"  variant?: 'primary' | 'secondary' | 'danger'  \u002F\u002F union = autocomplete + exhaustiveness\n",[34,73,75],{"class":36,"line":74},7,[34,76,77],{},"  size?: 'sm' | 'md' | 'lg'\n",[34,79,81],{"class":36,"line":80},8,[34,82,83],{},"  onClick: (event: React.MouseEvent\u003CHTMLButtonElement>) => void\n",[34,85,87],{"class":36,"line":86},9,[34,88,89],{},"  disabled?: boolean\n",[34,91,93],{"class":36,"line":92},10,[34,94,95],{},"  children?: ReactNode  \u002F\u002F anything renderable: string, element, array of elements\n",[34,97,99],{"class":36,"line":98},11,[34,100,101],{},"}\n",[34,103,105],{"class":36,"line":104},12,[34,106,47],{"emptyLinePlaceholder":46},[34,108,110],{"class":36,"line":109},13,[34,111,112],{},"function Button({ label, variant = 'primary', size = 'md', onClick, disabled, children }: ButtonProps) {\n",[34,114,116],{"class":36,"line":115},14,[34,117,118],{},"  return (\n",[34,120,122],{"class":36,"line":121},15,[34,123,124],{},"    \u003Cbutton\n",[34,126,128],{"class":36,"line":127},16,[34,129,130],{},"      className={`btn btn-${variant} btn-${size}`}\n",[34,132,134],{"class":36,"line":133},17,[34,135,136],{},"      onClick={onClick}\n",[34,138,140],{"class":36,"line":139},18,[34,141,142],{},"      disabled={disabled}\n",[34,144,146],{"class":36,"line":145},19,[34,147,148],{},"    >\n",[34,150,152],{"class":36,"line":151},20,[34,153,154],{},"      {label || children}\n",[34,156,158],{"class":36,"line":157},21,[34,159,160],{},"    \u003C\u002Fbutton>\n",[34,162,164],{"class":36,"line":163},22,[34,165,166],{},"  )\n",[34,168,170],{"class":36,"line":169},23,[34,171,101],{},[34,173,175],{"class":36,"line":174},24,[34,176,47],{"emptyLinePlaceholder":46},[34,178,180],{"class":36,"line":179},25,[34,181,182],{},"\u002F\u002F type vs interface: use `type` for unions and computed types, `interface` for\n",[34,184,186],{"class":36,"line":185},26,[34,187,188],{},"\u002F\u002F object shapes that might be extended. For props, `interface` is conventional\n",[34,190,192],{"class":36,"line":191},27,[34,193,194],{},"\u002F\u002F but `type` works identically. Prefer `type` if you don't need declaration merging.\n",[14,196,198],{"id":197},"discriminated-unions-for-variant-props","Discriminated Unions for Variant Props",[19,200,202],{"filename":201,"language":22},"discriminated_unions.tsx",[24,203,205],{"className":26,"code":204,"language":28,"meta":29,"style":29},"\u002F\u002F DISCRIMINATED UNION: different prop shapes for different variants.\n\u002F\u002F The `variant` field is the discriminant — TypeScript narrows based on it.\n\ntype InputProps =\n  | { variant: 'text'; value: string; onChange: (v: string) => void; placeholder?: string }\n  | { variant: 'number'; value: number; onChange: (v: number) => void; min?: number; max?: number }\n  | { variant: 'checkbox'; checked: boolean; onChange: (v: boolean) => void; label: string }\n\nfunction Input(props: InputProps) {\n  switch (props.variant) {\n    case 'text':\n      \u002F\u002F TS knows: props is { variant: 'text'; value: string; ... }\n      \u002F\u002F props.min would be a TYPE ERROR here — text variant doesn't have min\n      return \u003Cinput type=\"text\" value={props.value} placeholder={props.placeholder}\n        onChange={e => props.onChange(e.target.value)} \u002F>\n\n    case 'number':\n      \u002F\u002F TS knows: props is { variant: 'number'; value: number; min?: number; ... }\n      return \u003Cinput type=\"number\" value={props.value} min={props.min} max={props.max}\n        onChange={e => props.onChange(Number(e.target.value))} \u002F>\n\n    case 'checkbox':\n      \u002F\u002F TS knows: props is { variant: 'checkbox'; checked: boolean; label: string }\n      return \u003Clabel>\u003Cinput type=\"checkbox\" checked={props.checked}\n        onChange={e => props.onChange(e.target.checked)} \u002F> {props.label}\u003C\u002Flabel>\n  }\n}\n\n\u002F\u002F Usage: \u003CInput variant=\"text\" value=\"hello\" onChange={setStr} \u002F>\n\u002F\u002F TS enforces the correct prop shape per variant — wrong props are type errors.\n",[31,206,207,212,217,221,226,231,236,241,245,250,255,260,265,270,275,280,284,289,294,299,304,308,313,318,323,328,333,337,342,348],{"__ignoreMap":29},[34,208,209],{"class":36,"line":37},[34,210,211],{},"\u002F\u002F DISCRIMINATED UNION: different prop shapes for different variants.\n",[34,213,214],{"class":36,"line":43},[34,215,216],{},"\u002F\u002F The `variant` field is the discriminant — TypeScript narrows based on it.\n",[34,218,219],{"class":36,"line":50},[34,220,47],{"emptyLinePlaceholder":46},[34,222,223],{"class":36,"line":56},[34,224,225],{},"type InputProps =\n",[34,227,228],{"class":36,"line":62},[34,229,230],{},"  | { variant: 'text'; value: string; onChange: (v: string) => void; placeholder?: string }\n",[34,232,233],{"class":36,"line":68},[34,234,235],{},"  | { variant: 'number'; value: number; onChange: (v: number) => void; min?: number; max?: number }\n",[34,237,238],{"class":36,"line":74},[34,239,240],{},"  | { variant: 'checkbox'; checked: boolean; onChange: (v: boolean) => void; label: string }\n",[34,242,243],{"class":36,"line":80},[34,244,47],{"emptyLinePlaceholder":46},[34,246,247],{"class":36,"line":86},[34,248,249],{},"function Input(props: InputProps) {\n",[34,251,252],{"class":36,"line":92},[34,253,254],{},"  switch (props.variant) {\n",[34,256,257],{"class":36,"line":98},[34,258,259],{},"    case 'text':\n",[34,261,262],{"class":36,"line":104},[34,263,264],{},"      \u002F\u002F TS knows: props is { variant: 'text'; value: string; ... }\n",[34,266,267],{"class":36,"line":109},[34,268,269],{},"      \u002F\u002F props.min would be a TYPE ERROR here — text variant doesn't have min\n",[34,271,272],{"class":36,"line":115},[34,273,274],{},"      return \u003Cinput type=\"text\" value={props.value} placeholder={props.placeholder}\n",[34,276,277],{"class":36,"line":121},[34,278,279],{},"        onChange={e => props.onChange(e.target.value)} \u002F>\n",[34,281,282],{"class":36,"line":127},[34,283,47],{"emptyLinePlaceholder":46},[34,285,286],{"class":36,"line":133},[34,287,288],{},"    case 'number':\n",[34,290,291],{"class":36,"line":139},[34,292,293],{},"      \u002F\u002F TS knows: props is { variant: 'number'; value: number; min?: number; ... }\n",[34,295,296],{"class":36,"line":145},[34,297,298],{},"      return \u003Cinput type=\"number\" value={props.value} min={props.min} max={props.max}\n",[34,300,301],{"class":36,"line":151},[34,302,303],{},"        onChange={e => props.onChange(Number(e.target.value))} \u002F>\n",[34,305,306],{"class":36,"line":157},[34,307,47],{"emptyLinePlaceholder":46},[34,309,310],{"class":36,"line":163},[34,311,312],{},"    case 'checkbox':\n",[34,314,315],{"class":36,"line":169},[34,316,317],{},"      \u002F\u002F TS knows: props is { variant: 'checkbox'; checked: boolean; label: string }\n",[34,319,320],{"class":36,"line":174},[34,321,322],{},"      return \u003Clabel>\u003Cinput type=\"checkbox\" checked={props.checked}\n",[34,324,325],{"class":36,"line":179},[34,326,327],{},"        onChange={e => props.onChange(e.target.checked)} \u002F> {props.label}\u003C\u002Flabel>\n",[34,329,330],{"class":36,"line":185},[34,331,332],{},"  }\n",[34,334,335],{"class":36,"line":191},[34,336,101],{},[34,338,340],{"class":36,"line":339},28,[34,341,47],{"emptyLinePlaceholder":46},[34,343,345],{"class":36,"line":344},29,[34,346,347],{},"\u002F\u002F Usage: \u003CInput variant=\"text\" value=\"hello\" onChange={setStr} \u002F>\n",[34,349,351],{"class":36,"line":350},30,[34,352,353],{},"\u002F\u002F TS enforces the correct prop shape per variant — wrong props are type errors.\n",[14,355,357],{"id":356},"typing-event-handlers","Typing Event Handlers",[19,359,361],{"filename":360,"language":22},"typing_events.tsx",[24,362,364],{"className":26,"code":363,"language":28,"meta":29,"style":29},"\u002F\u002F React provides typed event interfaces. The generic is the DOM element type.\n\nfunction Form() {\n  const [email, setEmail] = useState('')\n\n  \u002F\u002F React.ChangeEvent\u003CHTMLInputElement> — fires on input\u002Ftextarea\u002Fselect change\n  const handleChange = (e: React.ChangeEvent\u003CHTMLInputElement>) => {\n    setEmail(e.target.value)  \u002F\u002F TS knows e.target is HTMLInputElement → .value is string\n  }\n\n  \u002F\u002F React.MouseEvent\u003CHTMLButtonElement> — fires on click\n  const handleClick = (e: React.MouseEvent\u003CHTMLButtonElement>) => {\n    e.preventDefault()\n    \u002F\u002F TS knows e.currentTarget is HTMLButtonElement → .disabled, .form, etc.\n  }\n\n  \u002F\u002F React.KeyboardEvent\u003CHTMLDivElement> — fires on keydown\u002Fkeyup\n  const handleKeyDown = (e: React.KeyboardEvent\u003CHTMLDivElement>) => {\n    if (e.key === 'Enter') submit()\n    \u002F\u002F e.key is string; e.keyCode is deprecated; e.code gives physical key\n  }\n\n  \u002F\u002F React.FormEvent\u003CHTMLFormElement> — fires on form submit\n  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n    e.preventDefault()\n    const formData = new FormData(e.currentTarget)  \u002F\u002F e.currentTarget = \u003Cform>\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput value={email} onChange={handleChange} onKeyDown={handleKeyDown} \u002F>\n      \u003Cbutton onClick={handleClick}>Submit\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n",[31,365,366,371,375,380,385,389,394,399,404,408,412,417,422,427,432,436,440,445,450,455,460,464,468,473,478,482,487,491,495,499,504,510,516,522,527],{"__ignoreMap":29},[34,367,368],{"class":36,"line":37},[34,369,370],{},"\u002F\u002F React provides typed event interfaces. The generic is the DOM element type.\n",[34,372,373],{"class":36,"line":43},[34,374,47],{"emptyLinePlaceholder":46},[34,376,377],{"class":36,"line":50},[34,378,379],{},"function Form() {\n",[34,381,382],{"class":36,"line":56},[34,383,384],{},"  const [email, setEmail] = useState('')\n",[34,386,387],{"class":36,"line":62},[34,388,47],{"emptyLinePlaceholder":46},[34,390,391],{"class":36,"line":68},[34,392,393],{},"  \u002F\u002F React.ChangeEvent\u003CHTMLInputElement> — fires on input\u002Ftextarea\u002Fselect change\n",[34,395,396],{"class":36,"line":74},[34,397,398],{},"  const handleChange = (e: React.ChangeEvent\u003CHTMLInputElement>) => {\n",[34,400,401],{"class":36,"line":80},[34,402,403],{},"    setEmail(e.target.value)  \u002F\u002F TS knows e.target is HTMLInputElement → .value is string\n",[34,405,406],{"class":36,"line":86},[34,407,332],{},[34,409,410],{"class":36,"line":92},[34,411,47],{"emptyLinePlaceholder":46},[34,413,414],{"class":36,"line":98},[34,415,416],{},"  \u002F\u002F React.MouseEvent\u003CHTMLButtonElement> — fires on click\n",[34,418,419],{"class":36,"line":104},[34,420,421],{},"  const handleClick = (e: React.MouseEvent\u003CHTMLButtonElement>) => {\n",[34,423,424],{"class":36,"line":109},[34,425,426],{},"    e.preventDefault()\n",[34,428,429],{"class":36,"line":115},[34,430,431],{},"    \u002F\u002F TS knows e.currentTarget is HTMLButtonElement → .disabled, .form, etc.\n",[34,433,434],{"class":36,"line":121},[34,435,332],{},[34,437,438],{"class":36,"line":127},[34,439,47],{"emptyLinePlaceholder":46},[34,441,442],{"class":36,"line":133},[34,443,444],{},"  \u002F\u002F React.KeyboardEvent\u003CHTMLDivElement> — fires on keydown\u002Fkeyup\n",[34,446,447],{"class":36,"line":139},[34,448,449],{},"  const handleKeyDown = (e: React.KeyboardEvent\u003CHTMLDivElement>) => {\n",[34,451,452],{"class":36,"line":145},[34,453,454],{},"    if (e.key === 'Enter') submit()\n",[34,456,457],{"class":36,"line":151},[34,458,459],{},"    \u002F\u002F e.key is string; e.keyCode is deprecated; e.code gives physical key\n",[34,461,462],{"class":36,"line":157},[34,463,332],{},[34,465,466],{"class":36,"line":163},[34,467,47],{"emptyLinePlaceholder":46},[34,469,470],{"class":36,"line":169},[34,471,472],{},"  \u002F\u002F React.FormEvent\u003CHTMLFormElement> — fires on form submit\n",[34,474,475],{"class":36,"line":174},[34,476,477],{},"  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n",[34,479,480],{"class":36,"line":179},[34,481,426],{},[34,483,484],{"class":36,"line":185},[34,485,486],{},"    const formData = new FormData(e.currentTarget)  \u002F\u002F e.currentTarget = \u003Cform>\n",[34,488,489],{"class":36,"line":191},[34,490,332],{},[34,492,493],{"class":36,"line":339},[34,494,47],{"emptyLinePlaceholder":46},[34,496,497],{"class":36,"line":344},[34,498,118],{},[34,500,501],{"class":36,"line":350},[34,502,503],{},"    \u003Cform onSubmit={handleSubmit}>\n",[34,505,507],{"class":36,"line":506},31,[34,508,509],{},"      \u003Cinput value={email} onChange={handleChange} onKeyDown={handleKeyDown} \u002F>\n",[34,511,513],{"class":36,"line":512},32,[34,514,515],{},"      \u003Cbutton onClick={handleClick}>Submit\u003C\u002Fbutton>\n",[34,517,519],{"class":36,"line":518},33,[34,520,521],{},"    \u003C\u002Fform>\n",[34,523,525],{"class":36,"line":524},34,[34,526,166],{},[34,528,530],{"class":36,"line":529},35,[34,531,101],{},[14,533,535],{"id":534},"typing-hooks","Typing Hooks",[19,537,539],{"filename":538,"language":22},"typing_hooks.tsx",[24,540,542],{"className":26,"code":541,"language":28,"meta":29,"style":29},"\u002F\u002F useState: inferred from initial value, or explicit generic\nconst [count, setCount] = useState(0)           \u002F\u002F infers number\nconst [user, setUser] = useState\u003CUser | null>(null)  \u002F\u002F explicit: User or null\nconst [items, setItems] = useState\u003Cstring[]>([])     \u002F\u002F explicit: string array\n\n\u002F\u002F useRef: the generic is the type of .current\nconst inputRef = useRef\u003CHTMLInputElement>(null)  \u002F\u002F .current: HTMLInputElement | null\nconst timerRef = useRef\u003Cnumber | null>(null)     \u002F\u002F for setInterval\u002FsetTimeout IDs\n\u002F\u002F NOTE: useRef(null) → .current is null initially. Always null-check: inputRef.current?.focus()\n\n\u002F\u002F createContext: the generic is the context value type\ninterface ThemeContextValue { theme: 'light' | 'dark'; toggle: () => void }\nconst ThemeContext = createContext\u003CThemeContextValue | undefined>(undefined)\n\u002F\u002F undefined default → enables the \"outside provider\" check in useTheme():\nfunction useTheme() {\n  const ctx = useContext(ThemeContext)\n  if (ctx === undefined) throw new Error('useTheme must be used within ThemeProvider')\n  return ctx  \u002F\u002F TS knows ctx is ThemeContextValue (not undefined) after the throw\n}\n\n\u002F\u002F useReducer: type the state and action\ntype Action = { type: 'increment' } | { type: 'setStep'; step: number }\nconst [state, dispatch] = useReducer(reducer, initialState)\n\u002F\u002F dispatch is typed: dispatch({ type: 'increment' }) ✓\n\u002F\u002F dispatch({ type: 'setStep' }) ✗ — missing required `step` field\n\u002F\u002F dispatch({ type: 'decrement' }) ✗ — 'decrement' not in the Action union\n",[31,543,544,549,554,559,564,568,573,578,583,588,592,597,602,607,612,617,622,627,632,636,640,645,650,655,660,665],{"__ignoreMap":29},[34,545,546],{"class":36,"line":37},[34,547,548],{},"\u002F\u002F useState: inferred from initial value, or explicit generic\n",[34,550,551],{"class":36,"line":43},[34,552,553],{},"const [count, setCount] = useState(0)           \u002F\u002F infers number\n",[34,555,556],{"class":36,"line":50},[34,557,558],{},"const [user, setUser] = useState\u003CUser | null>(null)  \u002F\u002F explicit: User or null\n",[34,560,561],{"class":36,"line":56},[34,562,563],{},"const [items, setItems] = useState\u003Cstring[]>([])     \u002F\u002F explicit: string array\n",[34,565,566],{"class":36,"line":62},[34,567,47],{"emptyLinePlaceholder":46},[34,569,570],{"class":36,"line":68},[34,571,572],{},"\u002F\u002F useRef: the generic is the type of .current\n",[34,574,575],{"class":36,"line":74},[34,576,577],{},"const inputRef = useRef\u003CHTMLInputElement>(null)  \u002F\u002F .current: HTMLInputElement | null\n",[34,579,580],{"class":36,"line":80},[34,581,582],{},"const timerRef = useRef\u003Cnumber | null>(null)     \u002F\u002F for setInterval\u002FsetTimeout IDs\n",[34,584,585],{"class":36,"line":86},[34,586,587],{},"\u002F\u002F NOTE: useRef(null) → .current is null initially. Always null-check: inputRef.current?.focus()\n",[34,589,590],{"class":36,"line":92},[34,591,47],{"emptyLinePlaceholder":46},[34,593,594],{"class":36,"line":98},[34,595,596],{},"\u002F\u002F createContext: the generic is the context value type\n",[34,598,599],{"class":36,"line":104},[34,600,601],{},"interface ThemeContextValue { theme: 'light' | 'dark'; toggle: () => void }\n",[34,603,604],{"class":36,"line":109},[34,605,606],{},"const ThemeContext = createContext\u003CThemeContextValue | undefined>(undefined)\n",[34,608,609],{"class":36,"line":115},[34,610,611],{},"\u002F\u002F undefined default → enables the \"outside provider\" check in useTheme():\n",[34,613,614],{"class":36,"line":121},[34,615,616],{},"function useTheme() {\n",[34,618,619],{"class":36,"line":127},[34,620,621],{},"  const ctx = useContext(ThemeContext)\n",[34,623,624],{"class":36,"line":133},[34,625,626],{},"  if (ctx === undefined) throw new Error('useTheme must be used within ThemeProvider')\n",[34,628,629],{"class":36,"line":139},[34,630,631],{},"  return ctx  \u002F\u002F TS knows ctx is ThemeContextValue (not undefined) after the throw\n",[34,633,634],{"class":36,"line":145},[34,635,101],{},[34,637,638],{"class":36,"line":151},[34,639,47],{"emptyLinePlaceholder":46},[34,641,642],{"class":36,"line":157},[34,643,644],{},"\u002F\u002F useReducer: type the state and action\n",[34,646,647],{"class":36,"line":163},[34,648,649],{},"type Action = { type: 'increment' } | { type: 'setStep'; step: number }\n",[34,651,652],{"class":36,"line":169},[34,653,654],{},"const [state, dispatch] = useReducer(reducer, initialState)\n",[34,656,657],{"class":36,"line":174},[34,658,659],{},"\u002F\u002F dispatch is typed: dispatch({ type: 'increment' }) ✓\n",[34,661,662],{"class":36,"line":179},[34,663,664],{},"\u002F\u002F dispatch({ type: 'setStep' }) ✗ — missing required `step` field\n",[34,666,667],{"class":36,"line":185},[34,668,669],{},"\u002F\u002F dispatch({ type: 'decrement' }) ✗ — 'decrement' not in the Action union\n",[14,671,673],{"id":672},"typing-refs-and-forwardref","Typing Refs and forwardRef",[19,675,677],{"filename":676,"language":22},"typing_forwardref.tsx",[24,678,680],{"className":26,"code":679,"language":28,"meta":29,"style":29},"import { forwardRef, useRef, useImperativeHandle } from 'react'\n\n\u002F\u002F forwardRef: the first generic is the ref type (the DOM element), second is props\nconst FancyInput = forwardRef\u003CHTMLInputElement, FancyInputProps>(\n  function FancyInput(props, ref) {\n    return \u003Cinput ref={ref} {...props} \u002F>\n  }\n)\n\n\u002F\u002F useImperativeHandle: define a custom ref API type\ninterface InputHandle {\n  focus: () => void\n  clear: () => void\n  getValue: () => string\n}\n\nconst ControlledInput = forwardRef\u003CInputHandle, ControlledInputProps>(\n  function ControlledInput(props, ref) {\n    const inputRef = useRef\u003CHTMLInputElement>(null)\n    useImperativeHandle(ref, () => ({\n      focus: () => inputRef.current?.focus(),\n      clear: () => { if (inputRef.current) inputRef.current.value = '' },\n      getValue: () => inputRef.current?.value ?? '',\n    }), [])\n    return \u003Cinput ref={inputRef} {...props} \u002F>\n  }\n)\n\u002F\u002F Parent: const ref = useRef\u003CInputHandle>(null) → ref.current?.focus() is typed\n",[31,681,682,687,691,696,701,706,711,715,720,724,729,734,739,744,749,753,757,762,767,772,777,782,787,792,797,802,806,810],{"__ignoreMap":29},[34,683,684],{"class":36,"line":37},[34,685,686],{},"import { forwardRef, useRef, useImperativeHandle } from 'react'\n",[34,688,689],{"class":36,"line":43},[34,690,47],{"emptyLinePlaceholder":46},[34,692,693],{"class":36,"line":50},[34,694,695],{},"\u002F\u002F forwardRef: the first generic is the ref type (the DOM element), second is props\n",[34,697,698],{"class":36,"line":56},[34,699,700],{},"const FancyInput = forwardRef\u003CHTMLInputElement, FancyInputProps>(\n",[34,702,703],{"class":36,"line":62},[34,704,705],{},"  function FancyInput(props, ref) {\n",[34,707,708],{"class":36,"line":68},[34,709,710],{},"    return \u003Cinput ref={ref} {...props} \u002F>\n",[34,712,713],{"class":36,"line":74},[34,714,332],{},[34,716,717],{"class":36,"line":80},[34,718,719],{},")\n",[34,721,722],{"class":36,"line":86},[34,723,47],{"emptyLinePlaceholder":46},[34,725,726],{"class":36,"line":92},[34,727,728],{},"\u002F\u002F useImperativeHandle: define a custom ref API type\n",[34,730,731],{"class":36,"line":98},[34,732,733],{},"interface InputHandle {\n",[34,735,736],{"class":36,"line":104},[34,737,738],{},"  focus: () => void\n",[34,740,741],{"class":36,"line":109},[34,742,743],{},"  clear: () => void\n",[34,745,746],{"class":36,"line":115},[34,747,748],{},"  getValue: () => string\n",[34,750,751],{"class":36,"line":121},[34,752,101],{},[34,754,755],{"class":36,"line":127},[34,756,47],{"emptyLinePlaceholder":46},[34,758,759],{"class":36,"line":133},[34,760,761],{},"const ControlledInput = forwardRef\u003CInputHandle, ControlledInputProps>(\n",[34,763,764],{"class":36,"line":139},[34,765,766],{},"  function ControlledInput(props, ref) {\n",[34,768,769],{"class":36,"line":145},[34,770,771],{},"    const inputRef = useRef\u003CHTMLInputElement>(null)\n",[34,773,774],{"class":36,"line":151},[34,775,776],{},"    useImperativeHandle(ref, () => ({\n",[34,778,779],{"class":36,"line":157},[34,780,781],{},"      focus: () => inputRef.current?.focus(),\n",[34,783,784],{"class":36,"line":163},[34,785,786],{},"      clear: () => { if (inputRef.current) inputRef.current.value = '' },\n",[34,788,789],{"class":36,"line":169},[34,790,791],{},"      getValue: () => inputRef.current?.value ?? '',\n",[34,793,794],{"class":36,"line":174},[34,795,796],{},"    }), [])\n",[34,798,799],{"class":36,"line":179},[34,800,801],{},"    return \u003Cinput ref={inputRef} {...props} \u002F>\n",[34,803,804],{"class":36,"line":185},[34,805,332],{},[34,807,808],{"class":36,"line":191},[34,809,719],{},[34,811,812],{"class":36,"line":339},[34,813,814],{},"\u002F\u002F Parent: const ref = useRef\u003CInputHandle>(null) → ref.current?.focus() is typed\n",[14,816,818],{"id":817},"utility-types-for-props","Utility Types for Props",[19,820,822],{"filename":821,"language":22},"utility_types.tsx",[24,823,825],{"className":26,"code":824,"language":28,"meta":29,"style":29},"import { type ComponentProps, type PropsWithChildren, type HTMLAttributes } from 'react'\n\n\u002F\u002F ComponentProps\u003Ctypeof Button> — extract the prop types of an existing component\ntype ButtonProps = ComponentProps\u003Ctypeof Button>\n\u002F\u002F Useful for wrapping: function MyButton(props: ButtonProps) { return \u003CButton {...props} \u002F> }\n\n\u002F\u002F PropsWithChildren — adds `children?: ReactNode` to any props type\ntype CardProps = PropsWithChildren\u003C{ title: string }>\nfunction Card({ title, children }: CardProps) {\n  return \u003Cdiv>\u003Ch3>{title}\u003C\u002Fh3>{children}\u003C\u002Fdiv>\n}\n\n\u002F\u002F HTMLAttributes\u003CHTMLDivElement> — all standard HTML attributes for a div\ntype DivProps = HTMLAttributes\u003CHTMLDivElement>\n\u002F\u002F Includes onClick, className, style, role, aria-*, data-*, etc.\n\n\u002F\u002F Omit to remove props you're overriding\ntype CustomButtonProps = Omit\u003CComponentProps\u003C'button'>, 'onClick'> & {\n  onSelect: (id: string) => void  \u002F\u002F replace onClick with a different handler\n}\n\n\u002F\u002F Pick to select only specific props\ntype OnlyClickProps = Pick\u003CComponentProps\u003C'button'>, 'onClick' | 'disabled'>\n",[31,826,827,832,836,841,846,851,855,860,865,870,875,879,883,888,893,898,902,907,912,917,921,925,930],{"__ignoreMap":29},[34,828,829],{"class":36,"line":37},[34,830,831],{},"import { type ComponentProps, type PropsWithChildren, type HTMLAttributes } from 'react'\n",[34,833,834],{"class":36,"line":43},[34,835,47],{"emptyLinePlaceholder":46},[34,837,838],{"class":36,"line":50},[34,839,840],{},"\u002F\u002F ComponentProps\u003Ctypeof Button> — extract the prop types of an existing component\n",[34,842,843],{"class":36,"line":56},[34,844,845],{},"type ButtonProps = ComponentProps\u003Ctypeof Button>\n",[34,847,848],{"class":36,"line":62},[34,849,850],{},"\u002F\u002F Useful for wrapping: function MyButton(props: ButtonProps) { return \u003CButton {...props} \u002F> }\n",[34,852,853],{"class":36,"line":68},[34,854,47],{"emptyLinePlaceholder":46},[34,856,857],{"class":36,"line":74},[34,858,859],{},"\u002F\u002F PropsWithChildren — adds `children?: ReactNode` to any props type\n",[34,861,862],{"class":36,"line":80},[34,863,864],{},"type CardProps = PropsWithChildren\u003C{ title: string }>\n",[34,866,867],{"class":36,"line":86},[34,868,869],{},"function Card({ title, children }: CardProps) {\n",[34,871,872],{"class":36,"line":92},[34,873,874],{},"  return \u003Cdiv>\u003Ch3>{title}\u003C\u002Fh3>{children}\u003C\u002Fdiv>\n",[34,876,877],{"class":36,"line":98},[34,878,101],{},[34,880,881],{"class":36,"line":104},[34,882,47],{"emptyLinePlaceholder":46},[34,884,885],{"class":36,"line":109},[34,886,887],{},"\u002F\u002F HTMLAttributes\u003CHTMLDivElement> — all standard HTML attributes for a div\n",[34,889,890],{"class":36,"line":115},[34,891,892],{},"type DivProps = HTMLAttributes\u003CHTMLDivElement>\n",[34,894,895],{"class":36,"line":121},[34,896,897],{},"\u002F\u002F Includes onClick, className, style, role, aria-*, data-*, etc.\n",[34,899,900],{"class":36,"line":127},[34,901,47],{"emptyLinePlaceholder":46},[34,903,904],{"class":36,"line":133},[34,905,906],{},"\u002F\u002F Omit to remove props you're overriding\n",[34,908,909],{"class":36,"line":139},[34,910,911],{},"type CustomButtonProps = Omit\u003CComponentProps\u003C'button'>, 'onClick'> & {\n",[34,913,914],{"class":36,"line":145},[34,915,916],{},"  onSelect: (id: string) => void  \u002F\u002F replace onClick with a different handler\n",[34,918,919],{"class":36,"line":151},[34,920,101],{},[34,922,923],{"class":36,"line":157},[34,924,47],{"emptyLinePlaceholder":46},[34,926,927],{"class":36,"line":163},[34,928,929],{},"\u002F\u002F Pick to select only specific props\n",[34,931,932],{"class":36,"line":169},[34,933,934],{},"type OnlyClickProps = Pick\u003CComponentProps\u003C'button'>, 'onClick' | 'disabled'>\n",[14,936,938],{"id":937},"tips-tricks","💡 Tips & Tricks",[19,940,942],{"filename":941,"language":22},"tips.tsx",[24,943,945],{"className":26,"code":944,"language":28,"meta":29,"style":29},"\u002F\u002F [Idiom] Use `type` for props (not `interface`) when you need unions, intersections,\n\u002F\u002F or utility types. Use `interface` when you need declaration merging (rare for props).\n\n\u002F\u002F [Idiom] For event handlers passed as props, type them explicitly:\n\u002F\u002F   onChange: (value: string) => void  — not (e: any) => void\n\u002F\u002F This makes the component's API self-documenting and catches type mismatches.\n\n\u002F\u002F [Idiom] Prefer discriminated unions over optional props for variant components.\n\u002F\u002F `variant: 'text' | 'number'` with per-variant props catches more bugs than\n\u002F\u002F `type?: string; min?: number; checked?: boolean` with everything optional.\n\n\u002F\u002F [Debug] If TS complains about a ref being null, add the null check:\n\u002F\u002F   inputRef.current?.focus() — not inputRef.current.focus()\n\u002F\u002F useRef\u003CT>(null) → T | null, not T. The null check is required by the type.\n\n\u002F\u002F [Idiom] Type the return of custom hooks explicitly for complex returns:\n\u002F\u002F   function useFetch(url: string): { data: T | null; loading: boolean; error: string | null }\n",[31,946,947,952,957,961,966,971,976,980,985,990,995,999,1004,1009,1014,1018,1023],{"__ignoreMap":29},[34,948,949],{"class":36,"line":37},[34,950,951],{},"\u002F\u002F [Idiom] Use `type` for props (not `interface`) when you need unions, intersections,\n",[34,953,954],{"class":36,"line":43},[34,955,956],{},"\u002F\u002F or utility types. Use `interface` when you need declaration merging (rare for props).\n",[34,958,959],{"class":36,"line":50},[34,960,47],{"emptyLinePlaceholder":46},[34,962,963],{"class":36,"line":56},[34,964,965],{},"\u002F\u002F [Idiom] For event handlers passed as props, type them explicitly:\n",[34,967,968],{"class":36,"line":62},[34,969,970],{},"\u002F\u002F   onChange: (value: string) => void  — not (e: any) => void\n",[34,972,973],{"class":36,"line":68},[34,974,975],{},"\u002F\u002F This makes the component's API self-documenting and catches type mismatches.\n",[34,977,978],{"class":36,"line":74},[34,979,47],{"emptyLinePlaceholder":46},[34,981,982],{"class":36,"line":80},[34,983,984],{},"\u002F\u002F [Idiom] Prefer discriminated unions over optional props for variant components.\n",[34,986,987],{"class":36,"line":86},[34,988,989],{},"\u002F\u002F `variant: 'text' | 'number'` with per-variant props catches more bugs than\n",[34,991,992],{"class":36,"line":92},[34,993,994],{},"\u002F\u002F `type?: string; min?: number; checked?: boolean` with everything optional.\n",[34,996,997],{"class":36,"line":98},[34,998,47],{"emptyLinePlaceholder":46},[34,1000,1001],{"class":36,"line":104},[34,1002,1003],{},"\u002F\u002F [Debug] If TS complains about a ref being null, add the null check:\n",[34,1005,1006],{"class":36,"line":109},[34,1007,1008],{},"\u002F\u002F   inputRef.current?.focus() — not inputRef.current.focus()\n",[34,1010,1011],{"class":36,"line":115},[34,1012,1013],{},"\u002F\u002F useRef\u003CT>(null) → T | null, not T. The null check is required by the type.\n",[34,1015,1016],{"class":36,"line":121},[34,1017,47],{"emptyLinePlaceholder":46},[34,1019,1020],{"class":36,"line":127},[34,1021,1022],{},"\u002F\u002F [Idiom] Type the return of custom hooks explicitly for complex returns:\n",[34,1024,1025],{"class":36,"line":133},[34,1026,1027],{},"\u002F\u002F   function useFetch(url: string): { data: T | null; loading: boolean; error: string | null }\n",[14,1029,1031],{"id":1030},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,1033,1035],{"filename":1034,"language":22},"edge_cases.tsx",[24,1036,1038],{"className":26,"code":1037,"language":28,"meta":29,"style":29},"\u002F\u002F [Gotcha] useRef\u003CT>(null) creates a ref of type RefObject\u003CT | null>. You can't\n\u002F\u002F pass it to a DOM element that expects Ref\u003CT> without the null check.\n\u002F\u002F Use useRef\u003CT>(null!) to assert non-null ONLY if you're certain it's set\n\u002F\u002F before access — but prefer the safe null-check pattern.\n\n\u002F\u002F [Gotcha] Children type: ReactNode includes null, undefined, string, number,\n\u002F\u002F boolean, element, array. JSX.Element is narrower (only rendered elements).\n\u002F\u002F Use ReactNode for children props — JSX.Element is too restrictive.\n\n\u002F\u002F [Gotcha] Event handler generics must match the DOM element. React.ChangeEvent\n\u002F\u002F \u003CHTMLTextAreaElement> on an \u003Cinput> → e.target is textarea, not input →\n\u002F\u002F wrong property access compiles but is semantically wrong.\n\n\u002F\u002F [Gotcha] The `exactOptionalPropertyTypes` TS config changes optional prop\n\u002F\u002F behavior: { x?: string } no longer accepts { x: undefined }. This affects\n\u002F\u002F React props — check your tsconfig before assuming optional = allows undefined.\n\n\u002F\u002F [Gotcha] forwardRef components need explicit generic types — TS can't infer\n\u002F\u002F the ref type from the JSX. Always: forwardRef\u003CRefType, PropsType>(...)\n",[31,1039,1040,1045,1050,1055,1060,1064,1069,1074,1079,1083,1088,1093,1098,1102,1107,1112,1117,1121,1126],{"__ignoreMap":29},[34,1041,1042],{"class":36,"line":37},[34,1043,1044],{},"\u002F\u002F [Gotcha] useRef\u003CT>(null) creates a ref of type RefObject\u003CT | null>. You can't\n",[34,1046,1047],{"class":36,"line":43},[34,1048,1049],{},"\u002F\u002F pass it to a DOM element that expects Ref\u003CT> without the null check.\n",[34,1051,1052],{"class":36,"line":50},[34,1053,1054],{},"\u002F\u002F Use useRef\u003CT>(null!) to assert non-null ONLY if you're certain it's set\n",[34,1056,1057],{"class":36,"line":56},[34,1058,1059],{},"\u002F\u002F before access — but prefer the safe null-check pattern.\n",[34,1061,1062],{"class":36,"line":62},[34,1063,47],{"emptyLinePlaceholder":46},[34,1065,1066],{"class":36,"line":68},[34,1067,1068],{},"\u002F\u002F [Gotcha] Children type: ReactNode includes null, undefined, string, number,\n",[34,1070,1071],{"class":36,"line":74},[34,1072,1073],{},"\u002F\u002F boolean, element, array. JSX.Element is narrower (only rendered elements).\n",[34,1075,1076],{"class":36,"line":80},[34,1077,1078],{},"\u002F\u002F Use ReactNode for children props — JSX.Element is too restrictive.\n",[34,1080,1081],{"class":36,"line":86},[34,1082,47],{"emptyLinePlaceholder":46},[34,1084,1085],{"class":36,"line":92},[34,1086,1087],{},"\u002F\u002F [Gotcha] Event handler generics must match the DOM element. React.ChangeEvent\n",[34,1089,1090],{"class":36,"line":98},[34,1091,1092],{},"\u002F\u002F \u003CHTMLTextAreaElement> on an \u003Cinput> → e.target is textarea, not input →\n",[34,1094,1095],{"class":36,"line":104},[34,1096,1097],{},"\u002F\u002F wrong property access compiles but is semantically wrong.\n",[34,1099,1100],{"class":36,"line":109},[34,1101,47],{"emptyLinePlaceholder":46},[34,1103,1104],{"class":36,"line":115},[34,1105,1106],{},"\u002F\u002F [Gotcha] The `exactOptionalPropertyTypes` TS config changes optional prop\n",[34,1108,1109],{"class":36,"line":121},[34,1110,1111],{},"\u002F\u002F behavior: { x?: string } no longer accepts { x: undefined }. This affects\n",[34,1113,1114],{"class":36,"line":127},[34,1115,1116],{},"\u002F\u002F React props — check your tsconfig before assuming optional = allows undefined.\n",[34,1118,1119],{"class":36,"line":133},[34,1120,47],{"emptyLinePlaceholder":46},[34,1122,1123],{"class":36,"line":139},[34,1124,1125],{},"\u002F\u002F [Gotcha] forwardRef components need explicit generic types — TS can't infer\n",[34,1127,1128],{"class":36,"line":145},[34,1129,1130],{},"\u002F\u002F the ref type from the JSX. Always: forwardRef\u003CRefType, PropsType>(...)\n",[14,1132,1134],{"id":1133},"spot-the-bug","🧠 Spot the Bug",[1136,1137,1138,1139,1142],"p",{},"A component receives a ",[31,1140,1141],{},"ref"," prop but TypeScript says it's not a valid prop:",[19,1144,1146],{"filename":1145,"language":22},"spot_the_bug.tsx",[24,1147,1149],{"className":26,"code":1148,"language":28,"meta":29,"style":29},"function MyInput(props: { value: string; onChange: (v: string) => void; ref?: React.Ref\u003CHTMLInputElement> }) {\n  return \u003Cinput ref={props.ref} value={props.value} onChange={e => props.onChange(e.target.value)} \u002F>\n}\n\u002F\u002F Parent: \u003CMyInput ref={inputRef} ... \u002F> → TS error: ref is not a valid prop\n",[31,1150,1151,1156,1161,1165],{"__ignoreMap":29},[34,1152,1153],{"class":36,"line":37},[34,1154,1155],{},"function MyInput(props: { value: string; onChange: (v: string) => void; ref?: React.Ref\u003CHTMLInputElement> }) {\n",[34,1157,1158],{"class":36,"line":43},[34,1159,1160],{},"  return \u003Cinput ref={props.ref} value={props.value} onChange={e => props.onChange(e.target.value)} \u002F>\n",[34,1162,1163],{"class":36,"line":50},[34,1164,101],{},[34,1166,1167],{"class":36,"line":56},[34,1168,1169],{},"\u002F\u002F Parent: \u003CMyInput ref={inputRef} ... \u002F> → TS error: ref is not a valid prop\n",[1171,1172,1173,1177,1193,1221],"details",{},[1174,1175,1176],"summary",{},"Answer",[1136,1178,1179,1181,1182,1184,1185,1188,1189,1192],{},[31,1180,1141],{}," is not a regular prop in React — it's special. React intercepts ",[31,1183,1141],{}," and doesn't pass it through to ",[31,1186,1187],{},"props",". You can't type it as a regular prop and expect it to work. You must use ",[31,1190,1191],{},"forwardRef",":",[24,1194,1196],{"className":26,"code":1195,"language":28,"meta":29,"style":29},"const MyInput = forwardRef\u003CHTMLInputElement, Omit\u003CMyInputProps, 'ref'>>(\n  function MyInput(props, ref) {\n    return \u003Cinput ref={ref} value={props.value} onChange={e => props.onChange(e.target.value)} \u002F>\n  }\n)\n",[31,1197,1198,1203,1208,1213,1217],{"__ignoreMap":29},[34,1199,1200],{"class":36,"line":37},[34,1201,1202],{},"const MyInput = forwardRef\u003CHTMLInputElement, Omit\u003CMyInputProps, 'ref'>>(\n",[34,1204,1205],{"class":36,"line":43},[34,1206,1207],{},"  function MyInput(props, ref) {\n",[34,1209,1210],{"class":36,"line":50},[34,1211,1212],{},"    return \u003Cinput ref={ref} value={props.value} onChange={e => props.onChange(e.target.value)} \u002F>\n",[34,1214,1215],{"class":36,"line":56},[34,1216,332],{},[34,1218,1219],{"class":36,"line":62},[34,1220,719],{},[1136,1222,1223,1224,1227],{},"The same applies to ",[31,1225,1226],{},"key"," — React also intercepts it and doesn't pass it as a prop.",[14,1229,1231],{"id":1230},"key-takeaways","Key Takeaways",[19,1233,1235],{"filename":1234,"language":22},"key_takeaways.tsx",[24,1236,1238],{"className":26,"code":1237,"language":28,"meta":29,"style":29},"\u002F\u002F 1. Type props with interface or type. Use discriminated unions for variant\n\u002F\u002F    components — the discriminant gives exhaustive switch checking + autocomplete.\n\n\u002F\u002F 2. Event types: React.ChangeEvent\u003CT>, React.MouseEvent\u003CT>, React.KeyboardEvent\u003CT>,\n\u002F\u002F    React.FormEvent\u003CT>. The generic is the DOM element type → e.target is typed.\n\n\u002F\u002F 3. Hooks: useState\u003CT>(initial), useRef\u003CT>(null), createContext\u003CT | undefined>(undefined),\n\u002F\u002F    useReducer\u003CState, Action>. Always type the Action as a discriminated union.\n\n\u002F\u002F 4. forwardRef needs explicit generics: forwardRef\u003CRefType, PropsType>(...).\n\u002F\u002F    useImperativeHandle exposes a typed handle: forwardRef\u003CHandle, Props>.\n\n\u002F\u002F 5. Utility types: ComponentProps\u003Ctypeof X> (extract props), PropsWithChildren\u003CP>\n\u002F\u002F    (add children), Omit\u003CP, 'x'> (remove), Pick\u003CP, 'x' | 'y'> (select).\n\u002F\u002F    Use ReactNode (not JSX.Element) for children — it's the broader, correct type.\n",[31,1239,1240,1245,1250,1254,1259,1264,1268,1273,1278,1282,1287,1292,1296,1301,1306],{"__ignoreMap":29},[34,1241,1242],{"class":36,"line":37},[34,1243,1244],{},"\u002F\u002F 1. Type props with interface or type. Use discriminated unions for variant\n",[34,1246,1247],{"class":36,"line":43},[34,1248,1249],{},"\u002F\u002F    components — the discriminant gives exhaustive switch checking + autocomplete.\n",[34,1251,1252],{"class":36,"line":50},[34,1253,47],{"emptyLinePlaceholder":46},[34,1255,1256],{"class":36,"line":56},[34,1257,1258],{},"\u002F\u002F 2. Event types: React.ChangeEvent\u003CT>, React.MouseEvent\u003CT>, React.KeyboardEvent\u003CT>,\n",[34,1260,1261],{"class":36,"line":62},[34,1262,1263],{},"\u002F\u002F    React.FormEvent\u003CT>. The generic is the DOM element type → e.target is typed.\n",[34,1265,1266],{"class":36,"line":68},[34,1267,47],{"emptyLinePlaceholder":46},[34,1269,1270],{"class":36,"line":74},[34,1271,1272],{},"\u002F\u002F 3. Hooks: useState\u003CT>(initial), useRef\u003CT>(null), createContext\u003CT | undefined>(undefined),\n",[34,1274,1275],{"class":36,"line":80},[34,1276,1277],{},"\u002F\u002F    useReducer\u003CState, Action>. Always type the Action as a discriminated union.\n",[34,1279,1280],{"class":36,"line":86},[34,1281,47],{"emptyLinePlaceholder":46},[34,1283,1284],{"class":36,"line":92},[34,1285,1286],{},"\u002F\u002F 4. forwardRef needs explicit generics: forwardRef\u003CRefType, PropsType>(...).\n",[34,1288,1289],{"class":36,"line":98},[34,1290,1291],{},"\u002F\u002F    useImperativeHandle exposes a typed handle: forwardRef\u003CHandle, Props>.\n",[34,1293,1294],{"class":36,"line":104},[34,1295,47],{"emptyLinePlaceholder":46},[34,1297,1298],{"class":36,"line":109},[34,1299,1300],{},"\u002F\u002F 5. Utility types: ComponentProps\u003Ctypeof X> (extract props), PropsWithChildren\u003CP>\n",[34,1302,1303],{"class":36,"line":115},[34,1304,1305],{},"\u002F\u002F    (add children), Omit\u003CP, 'x'> (remove), Pick\u003CP, 'x' | 'y'> (select).\n",[34,1307,1308],{"class":36,"line":121},[34,1309,1310],{},"\u002F\u002F    Use ReactNode (not JSX.Element) for children — it's the broader, correct type.\n",[1312,1313,1314],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":29,"searchDepth":43,"depth":43,"links":1316},[1317,1318,1319,1320,1321,1322,1323,1324,1325,1326],{"id":16,"depth":43,"text":17},{"id":197,"depth":43,"text":198},{"id":356,"depth":43,"text":357},{"id":534,"depth":43,"text":535},{"id":672,"depth":43,"text":673},{"id":817,"depth":43,"text":818},{"id":937,"depth":43,"text":938},{"id":1030,"depth":43,"text":1031},{"id":1133,"depth":43,"text":1134},{"id":1230,"depth":43,"text":1231},"Typing props, state, hooks, events, refs, and context in React. Discriminated unions for component variants, utility types, generics, and common type pitfalls. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F23-typescript-with-react",{"title":5,"description":1327},"react\u002F23-typescript-with-react","kCluBqJm5Kg9aRx8YXc5RFu4TmXrDfio_b_2JRNt8Cw",1789924652523]