[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-react-weekly-compiler-memoization":44,"search-suggestions":58},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"slug":45,"title":46,"summary":47,"html":48,"css":49,"js":50,"config":51,"doc":53},"react-weekly-compiler-memoization","React Compiler & memoization — interactive playground",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 React Compiler & memoization, live\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Type in the search box. With memoization off, every card re-renders on every keystroke —\n      watch the log. Flip \u003Cb>Apply manual memoization\u003C\u002Fb> and the same keystrokes stop touching\n      the cards at all. This is the exact behavior React Compiler gives you automatically, from\n      the plain unmemoized code, with no toggle to flip yourself.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"banner info show\" id=\"boot\">Loading the real React 19.2.8 runtime from the CDN…\u003C\u002Fdiv>\n\n    \u003Cdiv id=\"app\" hidden>\n      \u003Cdiv class=\"panel\">\n        \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Search (this is the state that re-renders Dashboard)\u003C\u002Fdiv>\n        \u003Cinput id=\"query\" class=\"draft-input\" placeholder=\"Type anything…\" style=\"width:100%; box-sizing:border-box; padding:10px; border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--bg); color:inherit; font:inherit;\" \u002F>\n        \u003Cdiv class=\"presets\" style=\"margin-top:14px\">\n          \u003Clabel class=\"toggle\">\n            \u003Cinput type=\"checkbox\" id=\"memoToggle\" \u002F>\n            \u003Cspan class=\"switch\">\u003C\u002Fspan>\n            \u003Cspan>Apply manual memoization (\u003Ccode>memo\u003C\u002Fcode> + \u003Ccode>useMemo\u003C\u002Fcode> + \u003Ccode>useCallback\u003C\u002Fcode>)\u003C\u002Fspan>\n          \u003C\u002Flabel>\n          \u003Cbutton class=\"btn ghost sm\" id=\"resetBtn\" style=\"margin-left:10px\">Reset demo\u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"panel\" style=\"margin-top:16px\">\n        \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> Team roster\u003C\u002Fdiv>\n        \u003Cul class=\"roster\" id=\"roster\">\u003C\u002Ful>\n        \u003Cp class=\"hint\" id=\"verdictHint\" style=\"margin-top:12px\">\u003C\u002Fp>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"panel\" style=\"margin-top:16px\">\n        \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> What React actually did\u003C\u002Fdiv>\n        \u003Cp class=\"hint\" style=\"margin-top:0\">\n          Every line comes from a real \u003Ccode>useEffect\u003C\u002Fcode> that fires after each card commits —\n          this is React's own reconciler, observed, not narrated.\n        \u003C\u002Fp>\n        \u003Cdiv class=\"log\" id=\"log\" aria-live=\"polite\">\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"panel\" style=\"margin-top:16px\">\n        \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">4\u003C\u002Fspan> Model: what React Compiler would generate\u003C\u002Fdiv>\n        \u003Cp class=\"hint\" style=\"margin-top:0\">\n          \u003Cb>This panel does not execute.\u003C\u002Fb> React Compiler is a build-time Babel transform — it\n          cannot run inside a static HTML page. It shows what the compiler does with the plain,\n          unmemoized code from section 1 above: it produces the same \"cards don't re-render\"\n          result you see with the toggle \u003Cem>on\u003C\u002Fem>, without you writing \u003Ccode>memo\u003C\u002Fcode>,\n          \u003Ccode>useMemo\u003C\u002Fcode>, or \u003Ccode>useCallback\u003C\u002Fcode> anywhere.\n        \u003C\u002Fp>\n        \u003Cdiv class=\"compare\">\n          \u003Cdiv class=\"col\">\n            \u003Ch4>You write\u003C\u002Fh4>\n            \u003Cpre class=\"code\">\u003Ccode>function TeamRoster({ members, onInvite }) {\n  const sorted = sortByActivity(members);\n  return (\n    &lt;ul&gt;\n      {sorted.map((m) =&gt; (\n        &lt;MemberCard\n          key={m.id}\n          member={m}\n          onInvite={() =&gt; onInvite(m.id)}\n        \u002F&gt;\n      ))}\n    &lt;\u002Ful&gt;\n  );\n}\u003C\u002Fcode>\u003C\u002Fpre>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"col\">\n            \u003Ch4>Compiler analyzes &amp; memoizes\u003C\u002Fh4>\n            \u003Cpre class=\"code\">\u003Ccode>\u002F\u002F Illustrative — the real output is generated\n\u002F\u002F HIR, not source you'd hand-write or read.\nfunction TeamRoster({ members, onInvite }) {\n  const sorted = $[0] !== members\n    ? sortByActivity(members)\n    : $[1];\n  \u002F\u002F ...each &lt;MemberCard&gt; element is cached\n  \u002F\u002F the same way, keyed on its own inputs.\n}\u003C\u002Fcode>\u003C\u002Fpre>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>","\u002F*\n * Shared playground design system — daily-post\n * ============================================\n * Link it from any post's playground\u002Findex.html (no per-post boilerplate):\n *\n *   \u003Clink rel=\"stylesheet\" href=\"..\u002F..\u002F..\u002Fplayground\u002Fassets\u002Fbase.css\" \u002F>\n *\n * The relative path resolves the same locally (posts\u002F\u003Cbase>\u002Fplayground\u002F) and on\n * the deployed host (public\u002Fposts\u002F\u003Cbase>\u002Fplayground\u002F), because build-site.js\n * mirrors this file to public\u002Fplayground\u002Fassets\u002Fbase.css. For a CodeSandbox\n * --embed, playground.js inlines this file so the single-file sandbox still styles.\n *\n * Then write ONLY the demo-specific CSS in your inline \u003Cstyle>. Everything below\n * — tokens, layout, panels, buttons, toggles, sliders, code blocks, status\u002Flog,\n * banners, presets — comes for free and looks consistent across every post.\n *\n * Theme-aware: dark by default, light via prefers-color-scheme AND a\n * [data-theme] override on \u003Chtml> (so a toggle can force either). Accessible:\n * real focus rings, adequate contrast, reduced-motion honored.\n *\u002F\n\n\u002F* ---------- design tokens ---------- *\u002F\n:root {\n  --bg: #0d1117;\n  --bg-grid: #ffffff0a;\n  --panel: #161b22;\n  --panel-2: #0b0e14;\n  --card: #0b0e14;\n  --ink: #e8ebf2;\n  --muted: #9aa4b2;\n  --faint: #6b7688;\n  --line: #232a35;\n  --line-soft: #ffffff12;\n  --accent: #7c9cff;\n  --accent-ink: #0b0e14;\n  --good: #3fb950;\n  --warn: #f0883e;\n  --bad: #f85149;\n  --info: #58a6ff;\n  --radius: 14px;\n  --radius-sm: 9px;\n  --shadow: 0 8px 30px #0006;\n  --shadow-sm: 0 4px 14px #0005;\n  --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n  --sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n  color-scheme: dark;\n}\n\n\u002F* Light theme — system preference, overridable by [data-theme] on \u003Chtml>. *\u002F\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme=\"dark\"]) {\n    --bg: #f4f6fb;\n    --bg-grid: #00000008;\n    --panel: #ffffff;\n    --panel-2: #f0f3f9;\n    --card: #f7f9fc;\n    --ink: #131722;\n    --muted: #5b6472;\n    --faint: #8a93a3;\n    --line: #e2e7f0;\n    --line-soft: #00000010;\n    --accent: #2f6df0;\n    --accent-ink: #ffffff;\n    --good: #1a7f37;\n    --warn: #bc4c00;\n    --bad: #cf222e;\n    --info: #0969da;\n    --shadow: 0 8px 30px #0000001f;\n    --shadow-sm: 0 4px 14px #00000014;\n    color-scheme: light;\n  }\n}\n:root[data-theme=\"light\"] {\n  --bg: #f4f6fb;\n  --bg-grid: #00000008;\n  --panel: #ffffff;\n  --panel-2: #f0f3f9;\n  --card: #f7f9fc;\n  --ink: #131722;\n  --muted: #5b6472;\n  --faint: #8a93a3;\n  --line: #e2e7f0;\n  --line-soft: #00000010;\n  --accent: #2f6df0;\n  --accent-ink: #ffffff;\n  --good: #1a7f37;\n  --warn: #bc4c00;\n  --bad: #cf222e;\n  --info: #0969da;\n  --shadow: 0 8px 30px #0000001f;\n  --shadow-sm: 0 4px 14px #00000014;\n  color-scheme: light;\n}\n\n\u002F* ---------- reset & base ---------- *\u002F\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  font-family: var(--sans);\n  color: var(--ink);\n  background:\n    radial-gradient(circle at 1px 1px, var(--bg-grid) 1px, transparent 0) 0 0 \u002F 24px 24px,\n    var(--bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(14px, 3vw, 26px);\n  line-height: 1.55;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n}\n\n\u002F* The single content column. *\u002F\n.pg {\n  width: 100%;\n  max-width: 860px;\n}\n\n\u002F* ---------- header ---------- *\u002F\n.pg-title {\n  font-size: clamp(1.15rem, 1rem + 1vw, 1.4rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  margin: 0 0 4px;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n}\n.pg-sub {\n  color: var(--muted);\n  font-size: 0.92rem;\n  line-height: 1.5;\n  margin: 0 0 16px;\n  max-width: 66ch;\n}\n.pg-sub strong, .pg-sub b { color: var(--ink); font-weight: 700; }\n.pg-sub code { font-family: var(--mono); background: var(--line-soft); color: var(--accent);\n  padding: 1px 6px; border-radius: 6px; font-size: 0.85em; }\n\n\u002F* ---------- panels ---------- *\u002F\n.panel {\n  background: var(--panel);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 16px 18px;\n  margin-bottom: 14px;\n  box-shadow: var(--shadow-sm);\n}\n.panel-label {\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--muted);\n  font-weight: 800;\n  margin: 0 0 13px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.panel-label .n {\n  display: inline-grid; place-items: center;\n  width: 18px; height: 18px; border-radius: 50%;\n  background: var(--accent); color: var(--accent-ink);\n  font-size: 0.66rem; font-weight: 800;\n}\n\n\u002F* ---------- layout helpers ---------- *\u002F\n.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n.row.between { justify-content: space-between; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.grow { flex: 1 1 0; min-width: 0; }\n.wrap-controls { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: flex-end; }\n.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }\n.field > .label, .ctl-label {\n  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;\n  color: var(--muted); font-weight: 800;\n}\n\n\u002F* ---------- buttons ---------- *\u002F\n.btn {\n  font: inherit; font-weight: 650; font-size: 0.88rem;\n  cursor: pointer; user-select: none;\n  border: 1px solid var(--line);\n  background: var(--card); color: var(--ink);\n  padding: 9px 15px; border-radius: var(--radius-sm);\n  display: inline-flex; align-items: center; gap: 8px;\n  transition: background .15s ease, border-color .15s ease, transform .05s ease, color .15s ease;\n}\n.btn:hover { background: var(--line-soft); border-color: var(--muted); }\n.btn:active { transform: translateY(1px); }\n.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.btn[disabled] { opacity: 0.45; cursor: not-allowed; }\n.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }\n.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }\n.btn.good { background: var(--good); border-color: var(--good); color: #04140a; }\n.btn.bad  { background: var(--bad);  border-color: var(--bad);  color: #fff; }\n.btn.ghost { background: transparent; }\n.btn.sm { padding: 6px 11px; font-size: 0.82rem; }\n\n.btn-row { display: flex; flex-wrap: wrap; gap: 9px; }\n\n\u002F* Segmented control — a row of buttons where one is active (aria-pressed). *\u002F\n.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);\n  border-radius: var(--radius-sm); padding: 3px; gap: 3px; }\n.seg button {\n  font: inherit; font-weight: 650; font-size: 0.84rem; cursor: pointer;\n  border: none; background: transparent; color: var(--muted);\n  padding: 7px 13px; border-radius: 7px;\n  transition: background .15s ease, color .15s ease;\n}\n.seg button:hover { color: var(--ink); }\n.seg button[aria-pressed=\"true\"] { background: var(--accent); color: var(--accent-ink); }\n.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* Preset chips — worked-example shortcuts. *\u002F\n.presets { display: flex; flex-wrap: wrap; gap: 8px; }\n.presets .btn { background: var(--panel-2); }\n\n\u002F* ---------- toggle switch ---------- *\u002F\n.toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n  font-size: 0.88rem; font-weight: 600; user-select: none; }\n.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }\n.switch { position: relative; width: 44px; height: 25px; border-radius: 999px; flex: none;\n  background: var(--line); border: 1px solid var(--line); transition: background .18s ease; }\n.switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;\n  border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; box-shadow: 0 1px 3px #0006; }\n.toggle input:checked + .switch { background: var(--good); border-color: var(--good); }\n.toggle input:checked + .switch::after { transform: translateX(19px); background: #fff; }\n.toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* ---------- range slider ---------- *\u002F\ninput[type=\"range\"] {\n  -webkit-appearance: none; appearance: none; width: 100%; height: 6px;\n  background: var(--line); border-radius: 999px; outline: none; cursor: pointer; margin: 8px 0;\n}\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;\n  background: var(--accent); border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]::-moz-range-thumb {\n  width: 19px; height: 19px; border-radius: 50%; background: var(--accent);\n  border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n\n\u002F* ---------- text inputs \u002F selects ---------- *\u002F\ninput[type=\"text\"], input[type=\"number\"], input[type=\"search\"], select, textarea {\n  font: inherit; color: var(--ink); background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 11px; width: 100%;\n}\ntextarea { font-family: var(--mono); font-size: 0.85rem; line-height: 1.6; resize: vertical; }\ninput:focus-visible, select:focus-visible, textarea:focus-visible {\n  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);\n}\n\n\u002F* ---------- status \u002F output \u002F log ---------- *\u002F\n.status {\n  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;\n  font-size: 0.9rem; font-weight: 600;\n  padding: 11px 14px; border-radius: var(--radius-sm);\n  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink);\n}\n.status.good { border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }\n.status.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }\n.status.bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }\n.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.good { background: var(--good); } .dot.warn { background: var(--warn); }\n.dot.bad { background: var(--bad); } .dot.info { background: var(--info); }\n\n.output { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; }\n.log { font-family: var(--mono); font-size: 0.8rem; line-height: 1.7; background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 14px;\n  max-height: 220px; overflow-y: auto; white-space: pre-wrap; }\n\n\u002F* ---------- code block + syntax tokens ---------- *\u002F\n.code {\n  font-family: var(--mono); font-size: 0.82rem; line-height: 1.7;\n  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; overflow-x: auto; white-space: pre; tab-size: 2;\n}\n.code .tok-sel  { color: var(--accent); }\n.code .tok-prop { color: #86e29b; }\n.code .tok-kw   { color: #ff9bcb; }\n.code .tok-str  { color: #f0c674; }\n.code .tok-num  { color: #f0883e; }\n.code .tok-cmt  { color: var(--faint); font-style: italic; }\n.code .hl { background: color-mix(in srgb, var(--good) 22%, transparent); border-radius: 4px; padding: 0 3px; }\n\n\u002F* ---------- hint \u002F caption ---------- *\u002F\n.hint { font-size: 0.83rem; color: var(--muted); line-height: 1.55; margin: 10px 0 0; }\n.hint code { font-family: var(--mono); color: var(--ink); background: var(--line-soft);\n  padding: 1px 5px; border-radius: 5px; font-size: 0.86em; }\n.hint strong { color: var(--ink); }\n\n\u002F* ---------- banner (feature-support \u002F notices) ---------- *\u002F\n.banner {\n  display: none; margin-bottom: 14px; padding: 11px 14px; border-radius: 11px;\n  font-size: 0.85rem; line-height: 1.5;\n  color: var(--ink); background: color-mix(in srgb, var(--warn) 12%, var(--panel));\n  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));\n}\n.banner.show { display: block; }\n.banner.info { background: color-mix(in srgb, var(--info) 12%, var(--panel));\n  border-color: color-mix(in srgb, var(--info) 45%, var(--line)); }\n.banner code { font-family: var(--mono); background: var(--line-soft); padding: 1px 5px; border-radius: 5px; }\n\n\u002F* ---------- misc atoms ---------- *\u002F\n.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700;\n  padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }\n.pill.good { color: var(--good); background: color-mix(in srgb, var(--good) 16%, transparent); }\n.pill.bad  { color: var(--bad);  background: color-mix(in srgb, var(--bad) 16%, transparent); }\n.mono { font-family: var(--mono); }\n.muted { color: var(--muted); }\n.big-num { font-family: var(--mono); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }\n\n\u002F* Optional theme toggle button (top-right). Wire it in JS if you want it. *\u002F\n.theme-btn { position: fixed; top: 12px; right: 12px; z-index: 20; }\n\n\u002F* ---------- responsive ---------- *\u002F\n@media (max-width: 560px) {\n  .panel { padding: 14px; }\n  .wrap-controls { gap: 14px; }\n  .btn { padding: 9px 13px; }\n}\n\n\u002F* ---------- reduced motion ---------- *\u002F\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }\n}\n\n\n\u002F* Demo-specific only — base.css owns tokens, panels, buttons, status, log. *\u002F\n  .roster { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }\n  .card {\n    display: flex; align-items: center; justify-content: space-between; gap: 10px;\n    border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 10px 12px;\n    background: var(--panel-2); transition: background 120ms ease;\n  }\n  .card.flash { background: color-mix(in srgb, var(--accent) 22%, var(--panel-2)); }\n  .card .who { display: flex; align-items: baseline; gap: 8px; }\n  .card .name { font-weight: 600; }\n  .card .activity { color: var(--muted); font-size: .85rem; }\n  .card .count {\n    font-variant-numeric: tabular-nums; font-size: .72rem; padding: 2px 8px;\n    border-radius: 999px; background: var(--bg); border: 1px solid var(--line); color: var(--muted);\n  }\n  .compare { display: grid; gap: 12px; grid-template-columns: 1fr; }\n  @media (min-width: 720px) { .compare { grid-template-columns: 1fr 1fr; } }\n  .compare .col h4 { margin: 0 0 6px; font-size: .82rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }","const $ = (id) => document.getElementById(id);\n\nlet React, createRoot;\ntry {\n  \u002F\u002F Pinned to the exact version the article verifies.\n  [React, { createRoot }] = await Promise.all([\n    import(\"https:\u002F\u002Fesm.sh\u002Freact@19.2.8\"),\n    import(\"https:\u002F\u002Fesm.sh\u002Freact-dom@19.2.8\u002Fclient?deps=react@19.2.8\"),\n  ]);\n} catch (err) {\n  $(\"boot\").className = \"banner show\";\n  $(\"boot\").innerHTML =\n    \"\u003Cb>React couldn't load from the CDN\u003C\u002Fb> — you're offline, or a network policy blocked esm.sh. \" +\n    \"This demo deliberately runs the \u003Cem>real\u003C\u002Fem> React reconciler rather than imitating it, so \" +\n    \"there's nothing honest to show without it. The article's code samples stand on their own; \" +\n    \"try again with a connection.\";\n  throw err;\n}\n\nconst { createElement: h, Fragment, useState, useEffect, useMemo, useCallback, useRef, memo } = React;\n\n$(\"boot\").hidden = true;\n$(\"app\").hidden = false;\n\nconst MEMBERS = [\n  { id: \"amy\", name: \"Amy Chen\", activity: \"3 commits today\" },\n  { id: \"bilal\", name: \"Bilal Hassan\", activity: \"in a meeting\" },\n  { id: \"cleo\", name: \"Cleo Adeyemi\", activity: \"reviewing a PR\" },\n  { id: \"dan\", name: \"Dan Okafor\", activity: \"idle 20m\" },\n];\n\n\u002F\u002F Deliberately not cheap-looking, so \"recomputed on every render\" reads as\n\u002F\u002F a real cost, exactly like the article's sortByActivity().\nfunction sortByActivity(members) {\n  return [...members].sort((a, b) => a.name.localeCompare(b.name));\n}\n\nconst logEl = $(\"log\");\nfunction logLine(text, cls = \"\") {\n  const el = document.createElement(\"div\");\n  el.className = cls;\n  el.textContent = text;\n  logEl.prepend(el);\n  while (logEl.childElementCount > 40) logEl.lastElementChild.remove();\n}\n\nfunction flashCard(id) {\n  const el = document.getElementById(\"card-\" + id);\n  if (!el) return;\n  el.classList.add(\"flash\");\n  setTimeout(() => el.classList.remove(\"flash\"), 220);\n}\n\n\u002F\u002F One real component, used by both the memoized and unmemoized paths — the\n\u002F\u002F only thing that changes is whether IT gets wrapped in memo() and whether\n\u002F\u002F the props it receives are stable.\nfunction MemberCardInner({ id, name, activity, onInvite }) {\n  const renders = useRef(0);\n  const mounted = useRef(false);\n  useEffect(() => {\n    renders.current++;\n    if (mounted.current) {\n      logLine(`🔄 ${name} re-rendered (×${renders.current})`);\n      flashCard(id);\n    } else {\n      mounted.current = true;\n    }\n  });\n  return h(\"li\", { className: \"card\", id: \"card-\" + id },\n    h(\"div\", { className: \"who\" },\n      h(\"span\", { className: \"name\" }, name),\n      h(\"span\", { className: \"activity\" }, activity)\n    ),\n    h(\"div\", { style: { display: \"flex\", alignItems: \"center\", gap: \"8px\" } },\n      h(\"span\", { className: \"count\" }, `renders: ${renders.current}`),\n      h(\"button\", { className: \"btn ghost sm\", onClick: () => onInvite(id) }, \"Invite\")\n    )\n  );\n}\nconst MemberCard = memo(MemberCardInner);\n\n\u002F\u002F Both variants mount directly into the \u003Cul id=\"roster\"> below via a\n\u002F\u002F Fragment — there is no extra wrapping element, only the \u003Cli> cards.\nfunction TeamRosterPlain({ members, onInvite }) {\n  const sorted = sortByActivity(members);\n  return h(Fragment, null,\n    sorted.map((m) => h(MemberCard, {\n      key: m.id, id: m.id, name: m.name, activity: m.activity,\n      onInvite: () => onInvite(m.id), \u002F\u002F fresh function every render — MemberCard sees a new prop\n    }))\n  );\n}\n\nfunction TeamRosterMemoized({ members, onInvite }) {\n  const sorted = useMemo(() => sortByActivity(members), [members]);\n  return h(Fragment, null,\n    sorted.map((m) => h(MemberCard, {\n      key: m.id, id: m.id, name: m.name, activity: m.activity,\n      onInvite, \u002F\u002F same stable function every render — MemberCard's props never change\n    }))\n  );\n}\n\n\u002F\u002F The search input and the toggle live outside React, as plain DOM elements\n\u002F\u002F in the page. This App component owns the real React state; a no-deps\n\u002F\u002F effect re-binds those elements to it after every render — the same\n\u002F\u002F bridging pattern episode one used for its contact buttons.\nfunction App() {\n  const [query, setQuery] = useState(\"\");\n  const [memoized, setMemoized] = useState(false);\n\n  const handleInvite = useCallback((id) => {\n    logLine(`✉️ invited ${MEMBERS.find((m) => m.id === id).name}`, \"good\");\n  }, []);\n\n  useEffect(() => {\n    $(\"query\").value = query;\n    $(\"query\").oninput = (e) => setQuery(e.target.value);\n    $(\"memoToggle\").checked = memoized;\n    $(\"memoToggle\").onchange = (e) => setMemoized(e.target.checked);\n    $(\"verdictHint\").textContent = memoized\n      ? \"Memoized: typing above re-renders Dashboard, but every MemberCard's props stay identical, so React bails out — no card re-renders.\"\n      : \"Unmemoized: typing above re-renders Dashboard → TeamRoster → every MemberCard, even though the members never changed.\";\n  });\n\n  const Roster = memoized ? TeamRosterMemoized : TeamRosterPlain;\n  return h(Roster, { members: MEMBERS, onInvite: handleInvite });\n}\n\nconst root = createRoot($(\"roster\"));\n\n$(\"resetBtn\").addEventListener(\"click\", () => {\n  logEl.innerHTML = \"\";\n  root.render(h(App, { key: \"reset-\" + Date.now() }));\n});\n\nroot.render(h(App));",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"React Compiler 1.0: What useMemo You Can Delete","React Compiler 1.0 is stable. Here's the re-render model it automates, which useMemo\u002FuseCallback\u002Fmemo calls you can delete, and what still needs you.","\u002Fmedia\u002Fcovers\u002Freact-weekly-compiler-memoization.png",{"slug":6},[59,63,67,71,75,79,83,86,90,94,98,101],{"slug":60,"name":61,"articles":62},"webdev","Webdev",81,{"slug":64,"name":65,"articles":66},"javascript","Javascript",68,{"slug":68,"name":69,"articles":70},"frontend","Frontend",67,{"slug":72,"name":73,"articles":74},"css","Css",29,{"slug":76,"name":77,"articles":78},"tutorial","Tutorial",17,{"slug":80,"name":81,"articles":82},"typescript","Typescript",11,{"slug":84,"name":85,"articles":82},"performance","Performance",{"slug":87,"name":88,"articles":89},"react","React",9,{"slug":91,"name":92,"articles":93},"grammar","Grammar",6,{"slug":95,"name":96,"articles":97},"html","Html",5,{"slug":99,"name":100,"articles":97},"node","Node",{"slug":102,"name":103,"articles":97},"browser","Browser"]