[{"data":1,"prerenderedAt":1242},["ShallowReactive",2],{"content-\u002Fplugins\u002Faio-design-system\u002Faio-react-minimal-effects":3,"children-\u002Fplugins\u002Faio-design-system\u002Faio-react-minimal-effects":1241},{"id":4,"title":5,"author":6,"body":7,"budget_tier":6,"build_tags":6,"created":6,"description":1232,"document_type":1233,"extension":1234,"game":6,"install":36,"investment_tier":6,"league":6,"meta":1235,"navigation":1236,"patch":6,"path":1237,"plugin":24,"profit_per_hour":6,"ratings":6,"seo":1238,"skills_count":6,"status":6,"stem":1239,"strategy_tier":6,"tags":6,"updated":6,"version":6,"weight":6,"__hash__":1240},"content\u002Fplugins\u002Faio-design-system\u002Faio-react-minimal-effects.md","aio-react-minimal-effects",null,{"type":8,"value":9,"toc":1164},"minimark",[10,37,42,52,57,61,64,69,76,86,89,95,99,105,223,227,230,238,242,245,248,252,262,276,280,284,287,292,300,305,349,363,367,378,386,390,395,398,404,408,411,417,421,424,430,434,437,443,447,450,454,460,464,470,474,480,484,490,494,500,504,510,514,520,532,536,540,546,550,556,560,563,567,573,577,583,588,592,595,599,605,609,615,619,623,629,633,639,643,646,650,656,660,666,672,676,680,686,690,696,700,704,710,714,720,724,728,734,738,744,748,751,757,761,767,771,782,788,792,798,803,814,818,898,909,913,998,1006,1010,1013,1115,1119],[11,12,13],"blockquote",{},[14,15,16,17,25,26,25,30,33,34],"p",{},"From plugin ",[18,19,21],"a",{"href":20},"\u002Fplugins\u002Faio-design-system",[22,23,24],"strong",{},"aio-design-system"," · ",[27,28,29],"code",{},"v1.0.2",[22,31,32],{},"Install:"," ",[27,35,36],{},"\u002Fplugin install aio-design-system@aiocean-plugins",[38,39,41],"h2",{"id":40},"environment","Environment",[43,44,45],"ul",{},[46,47,48,49],"li",{},"tsc: !",[27,50,51],{},"tsc --version 2>\u002Fdev\u002Fnull || echo \"NOT INSTALLED\"",[53,54,56],"h1",{"id":55},"react-19-minimal-effects","React 19 Minimal Effects",[38,58,60],{"id":59},"scan-mode-when-user-has-existing-react-code","Scan Mode (when user has existing React code)",[14,62,63],{},"Use this mode to actively audit a codebase for problematic useEffect patterns and fix them.",[65,66,68],"h3",{"id":67},"step-1-scan","Step 1: SCAN",[14,70,71,72,75],{},"Search the codebase for all ",[27,73,74],{},"useEffect"," usage:",[77,78,84],"pre",{"className":79,"code":81,"language":82,"meta":83},[80],"language-bash","grep -rn \"useEffect\" --include=\"*.tsx\" --include=\"*.ts\" --include=\"*.jsx\" --include=\"*.js\" src\u002F\n","bash","",[27,85,81],{"__ignoreMap":83},[14,87,88],{},"Also search for related anti-patterns:",[77,90,93],{"className":91,"code":92,"language":82,"meta":83},[80],"grep -rn \"forwardRef\\|useCallback\\|useMemo\" --include=\"*.tsx\" --include=\"*.ts\" src\u002F\n",[27,94,92],{"__ignoreMap":83},[65,96,98],{"id":97},"step-2-classify","Step 2: CLASSIFY",[14,100,101,102,104],{},"For each ",[27,103,74],{}," found, classify into one of these categories:",[106,107,108,124],"table",{},[109,110,111],"thead",{},[112,113,114,118,121],"tr",{},[115,116,117],"th",{},"Category",[115,119,120],{},"Signal",[115,122,123],{},"Severity",[125,126,127,144,160,176,193,209],"tbody",{},[112,128,129,136,141],{},[130,131,132,135],"td",{},[22,133,134],{},"Unnecessary"," (derived state)",[130,137,138],{},[27,139,140],{},"useEffect(() => setState(f(x)), [x])",[130,142,143],{},"HIGH - remove entirely",[112,145,146,152,157],{},[130,147,148,151],{},[22,149,150],{},"Resettable"," (state reset on prop)",[130,153,154],{},[27,155,156],{},"useEffect(() => setState(init), [prop])",[130,158,159],{},"HIGH - use key prop",[112,161,162,168,173],{},[130,163,164,167],{},[22,165,166],{},"Event-driven"," (user action response)",[130,169,170,172],{},[27,171,74],{}," triggered by user interaction state",[130,174,175],{},"MEDIUM - move to handler",[112,177,178,184,190],{},[130,179,180,183],{},[22,181,182],{},"Chain"," (effect triggers effect)",[130,185,186,187,189],{},"Multiple ",[27,188,74],{}," with interdependent state",[130,191,192],{},"MEDIUM - consolidate",[112,194,195,201,206],{},[130,196,197,200],{},[22,198,199],{},"Notification"," (parent callback in effect)",[130,202,203],{},[27,204,205],{},"useEffect(() => onChange(val), [val])",[130,207,208],{},"MEDIUM - call in event",[112,210,211,217,220],{},[130,212,213,216],{},[22,214,215],{},"Legitimate"," (external sync)",[130,218,219],{},"Data fetching, subscriptions, DOM listeners, third-party widgets",[130,221,222],{},"OK - keep or use library",[65,224,226],{"id":225},"step-3-report","Step 3: REPORT",[14,228,229],{},"Output a table sorted by severity:",[77,231,236],{"className":232,"code":234,"language":235},[233],"language-text","| File:Line | Category | Current Code (summary) | Recommended Fix |\n","text",[27,237,234],{"__ignoreMap":83},[65,239,241],{"id":240},"step-4-refactor","Step 4: REFACTOR",[14,243,244],{},"For each non-legitimate useEffect, apply the specific fix from the Reference patterns below. After refactoring, verify the component still works and no render loops were introduced.",[246,247],"hr",{},[38,249,251],{"id":250},"reference-mode-patterns-and-knowledge","Reference Mode (patterns and knowledge)",[14,253,254,255,257,258,261],{},"Most ",[27,256,74],{}," usage is wrong. Effects are for ",[22,259,260],{},"synchronizing with external systems",", not for:",[43,263,264,267,270,273],{},[46,265,266],{},"Computing derived values",[46,268,269],{},"Handling user events",[46,271,272],{},"Transforming data",[46,274,275],{},"Chaining state updates",[38,277,279],{"id":278},"react-19-features","React 19 Features",[65,281,283],{"id":282},"react-compiler-auto-memoization","React Compiler (Auto-Memoization)",[14,285,286],{},"React Compiler automatically optimizes components. Manual memoization is now optional.",[14,288,289],{},[22,290,291],{},"Setup (Vite):",[77,293,298],{"className":294,"code":296,"language":297,"meta":83},[295],"language-ts","\u002F\u002F vite.config.ts\nexport default defineConfig({\n  plugins: [\n    react({\n      babel: {\n        plugins: [[\"babel-plugin-react-compiler\", {}]],\n      },\n    }),\n  ],\n});\n","ts",[27,299,296],{"__ignoreMap":83},[14,301,302],{},[22,303,304],{},"What Compiler Does Automatically:",[106,306,307,317],{},[109,308,309],{},[112,310,311,314],{},[115,312,313],{},"Manual Code (Before)",[115,315,316],{},"Compiler Handles (After)",[125,318,319,329,339],{},[112,320,321,326],{},[130,322,323],{},[27,324,325],{},"React.memo(Component)",[130,327,328],{},"Auto-memoized renders",[112,330,331,336],{},[130,332,333],{},[27,334,335],{},"useMemo(() => val, [deps])",[130,337,338],{},"Auto-memoized values",[112,340,341,346],{},[130,342,343],{},[27,344,345],{},"useCallback(fn, [deps])",[130,347,348],{},"Auto-memoized callbacks",[14,350,351,354,355,358,359,362],{},[22,352,353],{},"Note:"," Existing ",[27,356,357],{},"useMemo","\u002F",[27,360,361],{},"useCallback"," still work - compiler is smart about duplicates.",[65,364,366],{"id":365},"ref-as-prop-no-forwardref","ref as Prop (No forwardRef)",[14,368,369,370,373,374,377],{},"React 19 treats ",[27,371,372],{},"ref"," as a regular prop. ",[27,375,376],{},"forwardRef"," is deprecated.",[77,379,384],{"className":380,"code":382,"language":383,"meta":83},[381],"language-tsx","\u002F\u002F OLD: React 18\nconst Button = React.forwardRef\u003CHTMLButtonElement, Props>((props, ref) => {\n  return \u003Cbutton ref={ref}>{props.children}\u003C\u002Fbutton>;\n});\n\n\u002F\u002F NEW: React 19\nfunction Button({ ref, children }: { ref?: React.Ref\u003CHTMLButtonElement>; children: React.ReactNode }) {\n  return \u003Cbutton ref={ref}>{children}\u003C\u002Fbutton>;\n}\n","tsx",[27,385,382],{"__ignoreMap":83},[65,387,389],{"id":388},"new-hooks","New Hooks",[391,392,394],"h4",{"id":393},"useactionstate-async-actions","useActionState (Async Actions)",[14,396,397],{},"Track pending\u002Ferror state for async actions without manual useState.",[77,399,402],{"className":400,"code":401,"language":383,"meta":83},[381],"\u002F\u002F OLD: Manual state management\nfunction Form() {\n  const [isPending, setIsPending] = useState(false);\n  const [error, setError] = useState(null);\n\n  async function handleSubmit(formData) {\n    setIsPending(true);\n    setError(null);\n    try {\n      await submitForm(formData);\n    } catch (e) {\n      setError(e);\n    } finally {\n      setIsPending(false);\n    }\n  }\n}\n\n\u002F\u002F NEW: useActionState\nfunction Form() {\n  const [state, submitAction, isPending] = useActionState(\n    async (prevState, formData) => {\n      const result = await submitForm(formData);\n      return result;\n    },\n    null \u002F\u002F initial state\n  );\n\n  return (\n    \u003Cform action={submitAction}>\n      \u003Cbutton disabled={isPending}>Submit\u003C\u002Fbutton>\n      {state?.error && \u003Cp>{state.error}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n",[27,403,401],{"__ignoreMap":83},[391,405,407],{"id":406},"useoptimistic-instant-ui-feedback","useOptimistic (Instant UI Feedback)",[14,409,410],{},"Show optimistic updates while server confirms.",[77,412,415],{"className":413,"code":414,"language":383,"meta":83},[381],"function TodoList({ todos }) {\n  const [optimisticTodos, addOptimisticTodo] = useOptimistic(\n    todos,\n    (state, newTodo) => [...state, { ...newTodo, pending: true }]\n  );\n\n  async function handleAdd(formData) {\n    const newTodo = { text: formData.get(\"text\") };\n    addOptimisticTodo(newTodo); \u002F\u002F Instant UI update\n    await saveTodo(newTodo);    \u002F\u002F Server confirms later\n  }\n\n  return (\n    \u003Cul>\n      {optimisticTodos.map((todo) => (\n        \u003Cli key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>\n          {todo.text}\n        \u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n",[27,416,414],{"__ignoreMap":83},[391,418,420],{"id":419},"useformstatus-form-state-without-prop-drilling","useFormStatus (Form State Without Prop Drilling)",[14,422,423],{},"Access form pending state from any nested component.",[77,425,428],{"className":426,"code":427,"language":383,"meta":83},[381],"function SubmitButton() {\n  const { pending } = useFormStatus(); \u002F\u002F No props needed!\n  return \u003Cbutton disabled={pending}>{pending ? \"Saving...\" : \"Save\"}\u003C\u002Fbutton>;\n}\n\nfunction Form() {\n  return (\n    \u003Cform action={submitAction}>\n      \u003Cinput name=\"email\" \u002F>\n      \u003CSubmitButton \u002F> {\u002F* Knows form state automatically *\u002F}\n    \u003C\u002Fform>\n  );\n}\n",[27,429,427],{"__ignoreMap":83},[391,431,433],{"id":432},"use-api-conditional-contextpromise","use() API (Conditional Context\u002FPromise)",[14,435,436],{},"Read Promise or Context conditionally (unlike hooks, can be called in conditions).",[77,438,441],{"className":439,"code":440,"language":383,"meta":83},[381],"function Comments({ commentsPromise }) {\n  \u002F\u002F Can be called conditionally!\n  if (someCondition) {\n    const comments = use(commentsPromise); \u002F\u002F Suspends until resolved\n    return \u003CCommentList comments={comments} \u002F>;\n  }\n  return null;\n}\n\n\u002F\u002F Conditional context\nfunction Theme({ showTheme }) {\n  if (showTheme) {\n    const theme = use(ThemeContext); \u002F\u002F Conditional context read\n    return \u003Cdiv style={{ color: theme.color }}>Themed\u003C\u002Fdiv>;\n  }\n  return \u003Cdiv>No theme\u003C\u002Fdiv>;\n}\n",[27,442,440],{"__ignoreMap":83},[38,444,446],{"id":445},"rules-of-react-compiler-enforced","Rules of React (Compiler Enforced)",[14,448,449],{},"These rules are enforced by React Compiler and eslint-plugin-react-hooks. Breaking them causes bugs.",[65,451,453],{"id":452},"_1-components-must-be-pure","1. Components Must Be Pure",[77,455,458],{"className":456,"code":457,"language":383,"meta":83},[381],"\u002F\u002F BAD: Side effect during render\nfunction Component() {\n  document.title = \"Hello\"; \u002F\u002F Side effect!\n  return \u003Cdiv \u002F>;\n}\n\n\u002F\u002F GOOD: Side effect in useEffect\nfunction Component() {\n  useEffect(() => {\n    document.title = \"Hello\";\n  }, []);\n  return \u003Cdiv \u002F>;\n}\n",[27,459,457],{"__ignoreMap":83},[65,461,463],{"id":462},"_2-props-and-state-are-immutable","2. Props and State Are Immutable",[77,465,468],{"className":466,"code":467,"language":383,"meta":83},[381],"\u002F\u002F BAD: Mutating props\nfunction Component({ items }) {\n  items.push(newItem); \u002F\u002F Mutation!\n  return \u003CList items={items} \u002F>;\n}\n\n\u002F\u002F GOOD: Create new array\nfunction Component({ items }) {\n  const newItems = [...items, newItem];\n  return \u003CList items={newItems} \u002F>;\n}\n",[27,469,467],{"__ignoreMap":83},[65,471,473],{"id":472},"_3-hooks-at-top-level-only","3. Hooks at Top Level Only",[77,475,478],{"className":476,"code":477,"language":383,"meta":83},[381],"\u002F\u002F BAD: Conditional hook\nfunction Component({ isLoggedIn }) {\n  if (isLoggedIn) {\n    useEffect(() => {}); \u002F\u002F Conditional!\n  }\n}\n\n\u002F\u002F GOOD: Condition inside hook\nfunction Component({ isLoggedIn }) {\n  useEffect(() => {\n    if (isLoggedIn) {\n      \u002F\u002F ...\n    }\n  }, [isLoggedIn]);\n}\n",[27,479,477],{"__ignoreMap":83},[38,481,483],{"id":482},"pattern-1-derived-state","Pattern 1: Derived State",[14,485,486,489],{},[22,487,488],{},"The #1 useEffect mistake."," If a value can be computed from existing state\u002Fprops, compute it during render.",[65,491,493],{"id":492},"bad-useeffect-setstate","BAD: useEffect + setState",[77,495,498],{"className":496,"code":497,"language":383,"meta":83},[381],"function Form() {\n  const [firstName, setFirstName] = useState(\"\");\n  const [lastName, setLastName] = useState(\"\");\n  const [fullName, setFullName] = useState(\"\");\n\n  \u002F\u002F BAD: Causes extra render, stale value flash\n  useEffect(() => {\n    setFullName(`${firstName} ${lastName}`);\n  }, [firstName, lastName]);\n}\n",[27,499,497],{"__ignoreMap":83},[65,501,503],{"id":502},"good-compute-during-render","GOOD: Compute During Render",[77,505,508],{"className":506,"code":507,"language":383,"meta":83},[381],"function Form() {\n  const [firstName, setFirstName] = useState(\"\");\n  const [lastName, setLastName] = useState(\"\");\n\n  \u002F\u002F GOOD: Computed inline, no extra render\n  const fullName = `${firstName} ${lastName}`;\n}\n",[27,509,507],{"__ignoreMap":83},[65,511,513],{"id":512},"good-usememo-for-expensive-calculations","GOOD: useMemo for Expensive Calculations",[77,515,518],{"className":516,"code":517,"language":383,"meta":83},[381],"function TodoList({ todos, filter }) {\n  \u002F\u002F Only recompute when todos or filter change\n  const visibleTodos = useMemo(() => filterTodos(todos, filter), [todos, filter]);\n}\n",[27,519,517],{"__ignoreMap":83},[14,521,522,525,526,528,529,531],{},[22,523,524],{},"Rule:"," If you have ",[27,527,140],{},", replace with ",[27,530,357],{}," or inline computation.",[38,533,535],{"id":534},"pattern-2-reset-state-on-prop-change","Pattern 2: Reset State on Prop Change",[65,537,539],{"id":538},"bad-useeffect-to-reset","BAD: useEffect to Reset",[77,541,544],{"className":542,"code":543,"language":383,"meta":83},[381],"function ProfilePage({ userId }) {\n  const [comment, setComment] = useState(\"\");\n\n  \u002F\u002F BAD: Renders with stale state, then resets\n  useEffect(() => {\n    setComment(\"\");\n  }, [userId]);\n}\n",[27,545,543],{"__ignoreMap":83},[65,547,549],{"id":548},"good-use-key-prop","GOOD: Use Key Prop",[77,551,554],{"className":552,"code":553,"language":383,"meta":83},[381],"function ProfilePage({ userId }) {\n  \u002F\u002F GOOD: Component remounts with fresh state\n  return \u003CProfile userId={userId} key={userId} \u002F>;\n}\n\nfunction Profile({ userId }) {\n  const [comment, setComment] = useState(\"\"); \u002F\u002F Fresh on key change\n}\n",[27,555,553],{"__ignoreMap":83},[38,557,559],{"id":558},"pattern-3-event-handlers-not-effects","Pattern 3: Event Handlers Not Effects",[14,561,562],{},"User interactions should be handled in event handlers, not effects.",[65,564,566],{"id":565},"bad-effect-for-user-action","BAD: Effect for User Action",[77,568,571],{"className":569,"code":570,"language":383,"meta":83},[381],"function ProductPage({ product, addToCart }) {\n  \u002F\u002F BAD: Runs on any product change, not user action\n  useEffect(() => {\n    if (product.isInCart) {\n      showNotification(\"Added to cart!\");\n    }\n  }, [product]);\n}\n",[27,572,570],{"__ignoreMap":83},[65,574,576],{"id":575},"good-handle-in-event","GOOD: Handle in Event",[77,578,581],{"className":579,"code":580,"language":383,"meta":83},[381],"function ProductPage({ product, addToCart }) {\n  function handleBuyClick() {\n    addToCart(product);\n    showNotification(\"Added to cart!\"); \u002F\u002F Same event, clear causality\n  }\n}\n",[27,582,580],{"__ignoreMap":83},[14,584,585,587],{},[22,586,524],{}," If code runs because user did something, put it in the event handler.",[38,589,591],{"id":590},"pattern-4-no-effect-chains","Pattern 4: No Effect Chains",[14,593,594],{},"Multiple effects updating state in sequence = render waterfall.",[65,596,598],{"id":597},"bad-chain-of-effects","BAD: Chain of Effects",[77,600,603],{"className":601,"code":602,"language":383,"meta":83},[381],"function Game() {\n  const [card, setCard] = useState(null);\n  const [goldCount, setGoldCount] = useState(0);\n  const [round, setRound] = useState(1);\n\n  \u002F\u002F Effect 1 triggers Effect 2 triggers Effect 3...\n  useEffect(() => {\n    if (card?.gold) setGoldCount((c) => c + 1);\n  }, [card]);\n\n  useEffect(() => {\n    if (goldCount > 3) {\n      setRound((r) => r + 1);\n      setGoldCount(0);\n    }\n  }, [goldCount]);\n}\n",[27,604,602],{"__ignoreMap":83},[65,606,608],{"id":607},"good-single-event-handler","GOOD: Single Event Handler",[77,610,613],{"className":611,"code":612,"language":383,"meta":83},[381],"function Game() {\n  const [card, setCard] = useState(null);\n  const [goldCount, setGoldCount] = useState(0);\n  const [round, setRound] = useState(1);\n\n  function handlePlaceCard(nextCard) {\n    setCard(nextCard);\n    if (nextCard.gold) {\n      if (goldCount \u003C 3) {\n        setGoldCount(goldCount + 1);\n      } else {\n        setGoldCount(0);\n        setRound(round + 1);\n      }\n    }\n  }\n}\n",[27,614,612],{"__ignoreMap":83},[38,616,618],{"id":617},"pattern-5-notify-parent-in-event","Pattern 5: Notify Parent in Event",[65,620,622],{"id":621},"bad-effect-to-notify-parent","BAD: Effect to Notify Parent",[77,624,627],{"className":625,"code":626,"language":383,"meta":83},[381],"function Toggle({ onChange }) {\n  const [isOn, setIsOn] = useState(false);\n\n  \u002F\u002F BAD: Effect runs after render, causes parent re-render\n  useEffect(() => {\n    onChange(isOn);\n  }, [isOn, onChange]);\n}\n",[27,628,626],{"__ignoreMap":83},[65,630,632],{"id":631},"good-notify-in-same-event","GOOD: Notify in Same Event",[77,634,637],{"className":635,"code":636,"language":383,"meta":83},[381],"function Toggle({ onChange }) {\n  const [isOn, setIsOn] = useState(false);\n\n  function handleClick() {\n    const nextIsOn = !isOn;\n    setIsOn(nextIsOn);\n    onChange(nextIsOn); \u002F\u002F Same event batch\n  }\n}\n",[27,638,636],{"__ignoreMap":83},[38,640,642],{"id":641},"pattern-6-data-fetching","Pattern 6: Data Fetching",[14,644,645],{},"Use libraries instead of raw useEffect for data fetching.",[65,647,649],{"id":648},"bad-raw-useeffect","BAD: Raw useEffect",[77,651,654],{"className":652,"code":653,"language":383,"meta":83},[381],"function SearchResults({ query }) {\n  const [results, setResults] = useState([]);\n  const [loading, setLoading] = useState(false);\n\n  useEffect(() => {\n    let ignore = false;\n    setLoading(true);\n    fetchResults(query).then((data) => {\n      if (!ignore) {\n        setResults(data);\n        setLoading(false);\n      }\n    });\n    return () => {\n      ignore = true;\n    };\n  }, [query]);\n}\n",[27,655,653],{"__ignoreMap":83},[65,657,659],{"id":658},"good-use-react-query-swr","GOOD: Use React Query \u002F SWR",[77,661,664],{"className":662,"code":663,"language":383,"meta":83},[381],"function SearchResults({ query }) {\n  const { data, isLoading } = useQuery({\n    queryKey: [\"search\", query],\n    queryFn: () => fetchResults(query),\n  });\n}\n",[27,665,663],{"__ignoreMap":83},[14,667,668,671],{},[22,669,670],{},"Why:"," Libraries handle caching, deduplication, race conditions, background refresh.",[38,673,675],{"id":674},"pattern-7-external-subscriptions","Pattern 7: External Subscriptions",[65,677,679],{"id":678},"bad-useeffect-for-subscriptions","BAD: useEffect for Subscriptions",[77,681,684],{"className":682,"code":683,"language":383,"meta":83},[381],"function ChatIndicator() {\n  const [isOnline, setIsOnline] = useState(true);\n\n  useEffect(() => {\n    const sub = subscribe((status) => setIsOnline(status));\n    return () => sub.unsubscribe();\n  }, []);\n}\n",[27,685,683],{"__ignoreMap":83},[65,687,689],{"id":688},"good-usesyncexternalstore","GOOD: useSyncExternalStore",[77,691,694],{"className":692,"code":693,"language":383,"meta":83},[381],"function ChatIndicator() {\n  const isOnline = useSyncExternalStore(\n    subscribe, \u002F\u002F subscribe function\n    getSnapshot, \u002F\u002F get current value\n  );\n}\n",[27,695,693],{"__ignoreMap":83},[38,697,699],{"id":698},"pattern-8-one-time-init","Pattern 8: One-Time Init",[65,701,703],{"id":702},"bad-multiple-empty-effects","BAD: Multiple Empty Effects",[77,705,708],{"className":706,"code":707,"language":383,"meta":83},[381],"function App() {\n  useEffect(() => {\n    initAnalytics();\n  }, []);\n  useEffect(() => {\n    loadUser();\n  }, []);\n  useEffect(() => {\n    checkPermissions();\n  }, []);\n}\n",[27,709,707],{"__ignoreMap":83},[65,711,713],{"id":712},"good-usemounteffect-hook","GOOD: useMountEffect Hook",[77,715,718],{"className":716,"code":717,"language":383,"meta":83},[381],"\u002F\u002F hooks\u002FuseMountEffect.ts\nexport function useMountEffect(fn: () => void | (() => void)) {\n  useEffect(fn, []);\n}\n\n\u002F\u002F Usage\nfunction App() {\n  useMountEffect(() => {\n    initAnalytics();\n    loadUser();\n    checkPermissions();\n  });\n}\n",[27,719,717],{"__ignoreMap":83},[38,721,723],{"id":722},"pattern-9-no-polling","Pattern 9: No Polling",[65,725,727],{"id":726},"bad-setinterval-polling","BAD: setInterval Polling",[77,729,732],{"className":730,"code":731,"language":383,"meta":83},[381],"useEffect(() => {\n  const interval = setInterval(checkStatus, 1000);\n  return () => clearInterval(interval);\n}, []);\n",[27,733,731],{"__ignoreMap":83},[65,735,737],{"id":736},"good-event-based","GOOD: Event-Based",[77,739,742],{"className":740,"code":741,"language":383,"meta":83},[381],"useMountEffect(() => {\n  checkStatus(); \u002F\u002F Initial\n\n  const onVisible = () => {\n    if (document.visibilityState === \"visible\") checkStatus();\n  };\n  document.addEventListener(\"visibilitychange\", onVisible);\n  return () => document.removeEventListener(\"visibilitychange\", onVisible);\n});\n",[27,743,741],{"__ignoreMap":83},[38,745,747],{"id":746},"when-useeffect-is-correct","When useEffect IS Correct",[14,749,750],{},"Use effects only for synchronizing with external systems:",[77,752,755],{"className":753,"code":754,"language":383,"meta":83},[381],"\u002F\u002F 1. Browser APIs\nuseEffect(() => {\n  const handler = (e) => setPosition({ x: e.clientX, y: e.clientY });\n  window.addEventListener(\"mousemove\", handler);\n  return () => window.removeEventListener(\"mousemove\", handler);\n}, []);\n\n\u002F\u002F 2. Third-party widgets\nuseEffect(() => {\n  const map = new MapWidget(ref.current);\n  map.setCenter(coordinates);\n  return () => map.destroy();\n}, [coordinates]);\n\n\u002F\u002F 3. Network (if not using library)\nuseEffect(() => {\n  let cancelled = false;\n  fetch(url).then((res) => {\n    if (!cancelled) setData(res);\n  });\n  return () => {\n    cancelled = true;\n  };\n}, [url]);\n",[27,756,754],{"__ignoreMap":83},[38,758,760],{"id":759},"important-nuance-reactive-sync-during-render","Important Nuance: Reactive Sync During Render",[14,762,763,766],{},[22,764,765],{},"\"No side effects during render\""," applies to EXTERNAL mutations, NOT to syncing derived state from reactive sources.",[65,768,770],{"id":769},"when-render-time-callbacks-are-correct","When Render-Time Callbacks ARE Correct",[14,772,773,774,777,778,781],{},"When using reactive primitives like ",[27,775,776],{},"useSyncExternalStore"," or tldraw's ",[27,779,780],{},"useValue",", calling callbacks during render is the CORRECT pattern:",[77,783,786],{"className":784,"code":785,"language":383,"meta":83},[381],"\u002F\u002F CORRECT: Sync from reactive source during render\nfunction ToolSyncWatcher({ onToolChange }) {\n  const editor = useEditor();\n  const currentToolId = useValue(\"tool\", () => editor.getCurrentToolId(), [editor]);\n\n  const mappedTool = TOOL_MAP[currentToolId];\n  if (mappedTool) {\n    onToolChange(mappedTool); \u002F\u002F OK: syncing reactive state to parent\n  }\n  return null;\n}\n",[27,787,785],{"__ignoreMap":83},[65,789,791],{"id":790},"why-not-useeffect-here","Why NOT useEffect Here",[77,793,796],{"className":794,"code":795,"language":383,"meta":83},[381],"\u002F\u002F WRONG: useEffect delays sync by 1 frame\nfunction ToolSyncWatcher({ onToolChange }) {\n  const currentToolId = useValue(\"tool\", () => editor.getCurrentToolId(), [editor]);\n\n  useEffect(() => {\n    const mappedTool = TOOL_MAP[currentToolId];\n    if (mappedTool) {\n      onToolChange(mappedTool);\n    }\n  }, [currentToolId, onToolChange]); \u002F\u002F Runs AFTER render = 1 frame delay = flicker\n}\n",[27,797,795],{"__ignoreMap":83},[14,799,800],{},[22,801,802],{},"Problems with useEffect here:",[43,804,805,808,811],{},[46,806,807],{},"useEffect runs AFTER paint → 1 frame delay → UI desync\u002Fflicker",[46,809,810],{},"Reactive primitives are designed for render-time sync",[46,812,813],{},"You're not mutating external world, just propagating derived state",[65,815,817],{"id":816},"the-distinction","The Distinction",[106,819,820,833],{},[109,821,822],{},[112,823,824,827,830],{},[115,825,826],{},"During Render",[115,828,829],{},"OK?",[115,831,832],{},"Why",[125,834,835,848,861,873,885],{},[112,836,837,842,845],{},[130,838,839],{},[27,840,841],{},"onToolChange(derivedValue)",[130,843,844],{},"YES",[130,846,847],{},"Propagating derived state from reactive source",[112,849,850,855,858],{},[130,851,852],{},[27,853,854],{},"document.title = \"Hello\"",[130,856,857],{},"NO",[130,859,860],{},"External DOM mutation",[112,862,863,868,870],{},[130,864,865],{},[27,866,867],{},"fetch(\"\u002Fapi\u002Flog\")",[130,869,857],{},[130,871,872],{},"External API call",[112,874,875,880,882],{},[130,876,877],{},[27,878,879],{},"console.log(\"rendered\")",[130,881,857],{},[130,883,884],{},"External I\u002FO",[112,886,887,893,895],{},[130,888,889,892],{},[27,890,891],{},"parent.setState(derived)"," from useValue",[130,894,844],{},[130,896,897],{},"React handles batching",[14,899,900,902,903,905,906,908],{},[22,901,524],{}," If syncing from reactive primitives (",[27,904,780],{},", ",[27,907,776],{},") to parent state, do it during render. Adding useEffect makes things worse by introducing a 1-frame delay.",[38,910,912],{"id":911},"checklist-before-useeffect","Checklist Before useEffect",[914,915,916,924,934,940,946,952,958,966,972,981,990],"ol",{},[46,917,918,921,922],{},[22,919,920],{},"Is this derived from state\u002Fprops?"," → Compute inline or ",[27,923,357],{},[46,925,926,929,930,933],{},[22,927,928],{},"Is this resetting state on prop change?"," → Use ",[27,931,932],{},"key"," prop",[46,935,936,939],{},[22,937,938],{},"Is this responding to user action?"," → Put in event handler",[46,941,942,945],{},[22,943,944],{},"Is this a chain of state updates?"," → Consolidate in single handler",[46,947,948,951],{},[22,949,950],{},"Is this notifying parent?"," → Call in same event",[46,953,954,957],{},[22,955,956],{},"Is this data fetching?"," → Use React Query\u002FSWR",[46,959,960,963,964],{},[22,961,962],{},"Is this subscribing to external store?"," → ",[27,965,776],{},[46,967,968,971],{},[22,969,970],{},"Is this polling?"," → Use events (visibility, focus, online)",[46,973,974,963,977,980],{},[22,975,976],{},"Is this one-time init?",[27,978,979],{},"useMountEffect"," or module-level",[46,982,983,929,986,989],{},[22,984,985],{},"Is this async form submission?",[27,987,988],{},"useActionState"," (React 19)",[46,991,992,929,995,989],{},[22,993,994],{},"Is this optimistic update?",[27,996,997],{},"useOptimistic",[14,999,1000,1003,1004],{},[22,1001,1002],{},"Only if none of the above:"," Use ",[27,1005,74],{},[38,1007,1009],{"id":1008},"code-review-checklist-react-19","Code Review Checklist (React 19)",[14,1011,1012],{},"When reviewing React code, flag these patterns:",[106,1014,1015,1028],{},[109,1016,1017],{},[112,1018,1019,1022,1025],{},[115,1020,1021],{},"Pattern",[115,1023,1024],{},"Issue",[115,1026,1027],{},"Fix",[125,1029,1030,1046,1061,1076,1089,1102],{},[112,1031,1032,1037,1040],{},[130,1033,1034],{},[27,1035,1036],{},"React.forwardRef()",[130,1038,1039],{},"Deprecated",[130,1041,1042,1043,1045],{},"Use ",[27,1044,372],{}," as prop",[112,1047,1048,1053,1056],{},[130,1049,1050],{},[27,1051,1052],{},"useEffect(() => setState(derived), [deps])",[130,1054,1055],{},"Derived state in effect",[130,1057,1058,1059],{},"Compute inline or ",[27,1060,357],{},[112,1062,1063,1069,1072],{},[130,1064,1065,1068],{},[27,1066,1067],{},"useState(isPending) + useState(error)"," for async",[130,1070,1071],{},"Manual async state",[130,1073,1074],{},[27,1075,988],{},[112,1077,1078,1083,1086],{},[130,1079,1080,1082],{},[27,1081,361],{}," everywhere",[130,1084,1085],{},"Over-memoization",[130,1087,1088],{},"Let compiler handle (or keep, harmless)",[112,1090,1091,1096,1099],{},[130,1092,1093,1095],{},[27,1094,74],{}," for form submission result",[130,1097,1098],{},"Effect for event",[130,1100,1101],{},"Handle in action\u002Fevent handler",[112,1103,1104,1107,1110],{},[130,1105,1106],{},"Prop drilling for form pending state",[130,1108,1109],{},"Unnecessary complexity",[130,1111,1112],{},[27,1113,1114],{},"useFormStatus",[38,1116,1118],{"id":1117},"references","References",[43,1120,1121,1129,1136,1143,1150,1157],{},[46,1122,1123],{},[18,1124,1128],{"href":1125,"rel":1126},"https:\u002F\u002Freact.dev\u002Flearn\u002Fyou-might-not-need-an-effect",[1127],"nofollow","You Might Not Need an Effect",[46,1130,1131],{},[18,1132,1135],{"href":1133,"rel":1134},"https:\u002F\u002Freact.dev\u002Freference\u002Frules",[1127],"Rules of React",[46,1137,1138],{},[18,1139,1142],{"href":1140,"rel":1141},"https:\u002F\u002Freact.dev\u002Flearn\u002Fseparating-events-from-effects",[1127],"Separating Events from Effects",[46,1144,1145],{},[18,1146,1149],{"href":1147,"rel":1148},"https:\u002F\u002Freact.dev\u002Flearn\u002Fsynchronizing-with-effects",[1127],"Synchronizing with Effects",[46,1151,1152],{},[18,1153,1156],{"href":1154,"rel":1155},"https:\u002F\u002Freact.dev\u002Fblog\u002F2024\u002F12\u002F05\u002Freact-19",[1127],"React 19 Blog Post",[46,1158,1159],{},[18,1160,1163],{"href":1161,"rel":1162},"https:\u002F\u002Freact.dev\u002Flearn\u002Freact-compiler",[1127],"React Compiler",{"title":83,"searchDepth":1165,"depth":1165,"links":1166},2,[1167,1168,1175,1176,1181,1186,1191,1195,1199,1203,1207,1211,1215,1219,1223,1224,1229,1230,1231],{"id":40,"depth":1165,"text":41},{"id":59,"depth":1165,"text":60,"children":1169},[1170,1172,1173,1174],{"id":67,"depth":1171,"text":68},3,{"id":97,"depth":1171,"text":98},{"id":225,"depth":1171,"text":226},{"id":240,"depth":1171,"text":241},{"id":250,"depth":1165,"text":251},{"id":278,"depth":1165,"text":279,"children":1177},[1178,1179,1180],{"id":282,"depth":1171,"text":283},{"id":365,"depth":1171,"text":366},{"id":388,"depth":1171,"text":389},{"id":445,"depth":1165,"text":446,"children":1182},[1183,1184,1185],{"id":452,"depth":1171,"text":453},{"id":462,"depth":1171,"text":463},{"id":472,"depth":1171,"text":473},{"id":482,"depth":1165,"text":483,"children":1187},[1188,1189,1190],{"id":492,"depth":1171,"text":493},{"id":502,"depth":1171,"text":503},{"id":512,"depth":1171,"text":513},{"id":534,"depth":1165,"text":535,"children":1192},[1193,1194],{"id":538,"depth":1171,"text":539},{"id":548,"depth":1171,"text":549},{"id":558,"depth":1165,"text":559,"children":1196},[1197,1198],{"id":565,"depth":1171,"text":566},{"id":575,"depth":1171,"text":576},{"id":590,"depth":1165,"text":591,"children":1200},[1201,1202],{"id":597,"depth":1171,"text":598},{"id":607,"depth":1171,"text":608},{"id":617,"depth":1165,"text":618,"children":1204},[1205,1206],{"id":621,"depth":1171,"text":622},{"id":631,"depth":1171,"text":632},{"id":641,"depth":1165,"text":642,"children":1208},[1209,1210],{"id":648,"depth":1171,"text":649},{"id":658,"depth":1171,"text":659},{"id":674,"depth":1165,"text":675,"children":1212},[1213,1214],{"id":678,"depth":1171,"text":679},{"id":688,"depth":1171,"text":689},{"id":698,"depth":1165,"text":699,"children":1216},[1217,1218],{"id":702,"depth":1171,"text":703},{"id":712,"depth":1171,"text":713},{"id":722,"depth":1165,"text":723,"children":1220},[1221,1222],{"id":726,"depth":1171,"text":727},{"id":736,"depth":1171,"text":737},{"id":746,"depth":1165,"text":747},{"id":759,"depth":1165,"text":760,"children":1225},[1226,1227,1228],{"id":769,"depth":1171,"text":770},{"id":790,"depth":1171,"text":791},{"id":816,"depth":1171,"text":817},{"id":911,"depth":1165,"text":912},{"id":1008,"depth":1165,"text":1009},{"id":1117,"depth":1165,"text":1118},"Review React code for unnecessary useEffect usage, refactor to React 19 patterns, and scan-fix problematic effects including derived state via effect+setState, effect chains, and polling patterns.","skill","md",{},true,"\u002Fplugins\u002Faio-design-system\u002Faio-react-minimal-effects",{"title":5,"description":1232},"plugins\u002Faio-design-system\u002Faio-react-minimal-effects","N1TjEm87F28p5PxtApzeBfKfzg5EzBQlEjFOTkzyvnQ",[],1779707416262]