[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-usesyncexternalstore-tearing-fix":93,"path-membership-usesyncexternalstore-tearing-fix":264,"related-usesyncexternalstore-tearing-fix":265,"code:js:true:b1t4ll":391,"code:js:true:h41yua":392,"code:js:true:j4d9br":393,"comments-01a14e81-eaa5-700a-a6f6-3be4ffe47c2b":394},[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,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",151,{"slug":50,"name":51,"articles":52},"javascript","Javascript",124,{"slug":54,"name":55,"articles":56},"frontend","Frontend",88,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",58,{"slug":62,"name":63,"articles":64},"css","Css",45,{"slug":66,"name":67,"articles":68},"performance","Performance",25,{"slug":70,"name":71,"articles":72},"typescript","Typescript",21,{"slug":74,"name":75,"articles":76},"react","React",19,{"slug":78,"name":79,"articles":80},"browser","Browser",14,{"slug":82,"name":83,"articles":84},"node","Node",13,{"slug":86,"name":87,"articles":88},"programming","Programming",12,{"slug":90,"name":91,"articles":92},"accessibility","Accessibility",10,{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":102,"likeCount":19,"commentCount":19,"author":103,"vertical":108,"topic":109,"tags":110,"_count":115,"playground":117,"body":119,"bodyMd":247,"seo":248,"translationGroupId":251,"series":97,"podcastUrl":97,"verticalId":5,"thread":252,"assessments":254,"translations":260,"quiz":262},"01a14e81-eaa5-700a-a6f6-3be4ffe47c2b","usesyncexternalstore-tearing-fix","Your Custom Hooks Tear in React 18. `useSyncExternalStore` Fixes It.",null,"A useState + useEffect hook that subscribes to window size, localStorage, or a WebSocket looks fine until React 18's concurrent rendering shows two parts of your UI two different values at once. useSyncExternalStore is the hook built to stop that.","\u002Fmedia\u002Fcovers\u002Fusesyncexternalstore-tearing-fix.png",5,"2026-10-23T06:11:03.306Z",24,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[111,112,113,114],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":54,"name":55,"color":97},{"assessments":116},1,{"slug":95,"title":118},"Catch the tear — useSyncExternalStore playground",{"blocks":120,"version":116},[121,125,128,134,137,141,144,147,150,153,156,159,162,165,169,172,175,178,181,184,187,190,193,196,200,203,206,210,213,216,219,222,225,228,231,234,237,240],{"id":122,"html":123,"type":124},"b1","\u003Cp>The header says &quot;You&#39;re offline.&quot; Three pixels below it, the save button is still clickable, sending requests like nothing&#39;s wrong. Same \u003Ccode>navigator.onLine\u003C\u002Fcode> value. Same render. Two different answers on screen at the same time.\u003C\u002Fp>","paragraph",{"id":126,"html":127,"type":124},"b2","\u003Cp>You didn&#39;t write a new bug. You wrote the hook everyone writes.\u003C\u002Fp>",{"id":129,"code":130,"type":131,"language":132,"highlight":133},"b3","function useOnlineStatus() {\n  const [online, setOnline] = useState(navigator.onLine);\n\n  useEffect(() => {\n    const update = () => setOnline(navigator.onLine);\n    window.addEventListener('online', update);\n    window.addEventListener('offline', update);\n    return () => {\n      window.removeEventListener('online', update);\n      window.removeEventListener('offline', update);\n    };\n  }, []);\n\n  return online;\n}","code","js",[],{"id":135,"html":136,"type":124},"b4","\u003Cp>It worked in every app you shipped before React 18. Then a teammate wraps a route transition in \u003Ccode>startTransition\u003C\u002Fcode> to keep the UI responsive, two components that both call \u003Ccode>useOnlineStatus()\u003C\u002Fcode> briefly disagree, and you spend an afternoon adding \u003Ccode>console.log\u003C\u002Fcode> statements to a bug that reproduces maybe one time in fifteen.\u003C\u002Fp>",{"id":138,"html":139,"text":139,"type":140,"level":31},"b5","Why a perfectly normal hook starts lying","heading",{"id":142,"html":143,"type":124},"b6","\u003Cp>\u003Ccode>useState\u003C\u002Fcode> + \u003Ccode>useEffect\u003C\u002Fcode> assumes something React 18 no longer guarantees: that once a component commits, it stays committed until the next explicit update, and that every component re-renders from the same world.\u003C\u002Fp>",{"id":145,"html":146,"type":124},"b7","\u003Cp>Concurrent rendering breaks that assumption on purpose. React can start rendering a tree, pause partway through to handle something more urgent — a keystroke, a transition — and resume later. If the external value your hook reads (\u003Ccode>navigator.onLine\u003C\u002Fcode>, \u003Ccode>window.innerWidth\u003C\u002Fcode>, a value in \u003Ccode>localStorage\u003C\u002Fcode>, a WebSocket payload) changes in that gap, some components finish rendering with the old value and some with the new one. Nothing crashes. You just get a UI where two places disagree about one fact, for one frame, with no error to catch.\u003C\u002Fp>",{"id":148,"html":149,"type":124},"b8","\u003Cp>This has a name: \u003Cstrong>tearing\u003C\u002Fstrong>. It&#39;s not a React bug — it&#39;s the cost of letting renders be interruptible, and it only shows up once a render \u003Cem>can\u003C\u002Fem> be interrupted.\u003C\u002Fp>",{"id":151,"html":152,"text":152,"type":140,"level":31},"b9","The hook was never wrong before — it was just never tested like this",{"id":154,"html":155,"type":124},"b10","\u003Cp>Here&#39;s the part that catches people: \u003Ccode>useState\u003C\u002Fcode> + \u003Ccode>useEffect\u003C\u002Fcode> isn&#39;t a bad pattern. It&#39;s the correct pattern for state React owns. The problem is specifically \u003Cstrong>external\u003C\u002Fstrong> state — state that lives outside React and changes on its own schedule, which is exactly what &quot;subscribe to a browser API&quot; always is.\u003C\u002Fp>",{"id":157,"html":158,"type":124},"b11","\u003Cp>A \u003Ccode>useEffect\u003C\u002Fcode>-based subscription updates state \u003Cem>after\u003C\u002Fem> the store already changed, through a callback, on React&#39;s own timing. It has no way to ask, right before this component paints, &quot;is this still current?&quot; It just trusts whatever \u003Ccode>setOnline\u003C\u002Fcode> it last received.\u003C\u002Fp>",{"id":160,"html":161,"text":161,"type":140,"level":31},"b12","The fix React shipped for exactly this",{"id":163,"html":164,"type":124},"b13","\u003Cp>\u003Ccode>useSyncExternalStore\u003C\u002Fcode> is the hook React 18 added so you stop hand-rolling this. The shape is unusual the first time you see it — three functions, no state:\u003C\u002Fp>",{"id":166,"code":167,"type":131,"language":132,"highlight":168},"b14","import { useSyncExternalStore } from 'react';\n\nfunction subscribe(callback) {\n  window.addEventListener('online', callback);\n  window.addEventListener('offline', callback);\n  return () => {\n    window.removeEventListener('online', callback);\n    window.removeEventListener('offline', callback);\n  };\n}\n\nfunction getSnapshot() {\n  return navigator.onLine;\n}\n\nfunction useOnlineStatus() {\n  return useSyncExternalStore(subscribe, getSnapshot);\n}",[],{"id":170,"html":171,"type":124},"b15","\u003Cp>\u003Ccode>subscribe\u003C\u002Fcode> wires up your listener once, same as the \u003Ccode>useEffect\u003C\u002Fcode> before. The difference is \u003Ccode>getSnapshot\u003C\u002Fcode> — a function that reads the \u003Cem>current\u003C\u002Fem> value directly, with nothing cached in React state. React calls it on every render, and critically, it re-checks it synchronously right before committing. If the store moved since the last check, React throws that render away and re-renders immediately with the fresh value, instead of letting a stale one paint. That synchronous re-check is the whole fix: it closes the exact gap where tearing happens.\u003C\u002Fp>",{"id":173,"html":174,"type":124},"b16","\u003Cp>You&#39;re not writing less code than before. You&#39;re handing React the one piece of information — &quot;read it fresh, right now&quot; — that a \u003Ccode>useState\u003C\u002Fcode> callback can never provide after the fact.\u003C\u002Fp>",{"id":176,"html":177,"type":124},"b17","\u003C!-- playground:start -->",{"id":179,"html":180,"text":180,"type":140,"level":31},"b18","🎮 Try it yourself",{"id":182,"html":183,"type":124},"b19","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fusesyncexternalstore-tearing-fix\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":185,"html":186,"type":124},"b20","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":188,"html":189,"type":124},"b21","\u003C!-- playground:end -->",{"id":191,"html":192,"text":192,"type":140,"level":31},"b22","The part that bites on the server",{"id":194,"html":195,"type":124},"b23","\u003Cp>\u003Ccode>useSyncExternalStore\u003C\u002Fcode> takes an optional third argument, \u003Ccode>getServerSnapshot\u003C\u002Fcode>, and skipping it is the next trap:\u003C\u002Fp>",{"id":197,"code":198,"type":131,"language":132,"highlight":199},"b24","function getServerSnapshot() {\n  return true; \u002F\u002F sensible default: assume online during SSR\n}\n\nuseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);",[],{"id":201,"html":202,"type":124},"b25","\u003Cp>There&#39;s no \u003Ccode>window\u003C\u002Fcode> on the server, so \u003Ccode>getSnapshot\u003C\u002Fcode> can&#39;t run there — \u003Ccode>navigator\u003C\u002Fcode> doesn&#39;t exist. If your component can render during SSR and you omit \u003Ccode>getServerSnapshot\u003C\u002Fcode>, React throws rather than guessing. It&#39;s not being pedantic: without it, the server-rendered HTML and the first client render would read different realities, and React would rather fail loudly than ship a hydration mismatch silently.\u003C\u002Fp>",{"id":204,"html":205,"type":124},"b26","\u003Cp>If your component is client-only (inside a dynamic import with SSR disabled, say), you can skip it. If it ever renders on the server, you can&#39;t.\u003C\u002Fp>",{"id":207,"html":208,"text":209,"type":140,"level":31},"b27","You&#39;ve already used this, you just didn&#39;t call it","You've already used this, you just didn't call it",{"id":211,"html":212,"type":124},"b28","\u003Cp>If you reach for Zustand or react-redux (v8+), you&#39;re already running on \u003Ccode>useSyncExternalStore\u003C\u002Fcode> — it&#39;s what those libraries&#39; React bindings switched to for exactly this reason. (Jotai, notably, chose not to — its author has written publicly about trading a small tearing risk for behavior that fits Suspense better.) You don&#39;t need to rewrite a working store library. You need this hook for the stores you wrote yourself: the \u003Ccode>useLocalStorage\u003C\u002Fcode>, \u003Ccode>useMediaQuery\u003C\u002Fcode>, or \u003Ccode>useWebSocketMessage\u003C\u002Fcode> hook sitting in your \u003Ccode>hooks\u002F\u003C\u002Fcode> folder, quietly subscribing to something outside React with a pattern that was correct until concurrent rendering gave it a seam to fall through.\u003C\u002Fp>",{"id":214,"html":215,"type":124},"b29","\u003C!-- quiz:start -->",{"id":217,"html":218,"text":218,"type":140,"level":31},"b30","🧠 Test yourself",{"id":220,"html":221,"type":124},"b31","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fusesyncexternalstore-tearing-fix\u002Fquiz\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":223,"html":224,"type":124},"b32","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":226,"html":227,"type":124},"b33","\u003C!-- quiz:end -->",{"id":229,"html":230,"type":124},"b34","\u003Cp>Go find your oldest \u003Ccode>useState\u003C\u002Fcode> + \u003Ccode>useEffect\u003C\u002Fcode>-plus-\u003Ccode>addEventListener\u003C\u002Fcode> hook. If it feeds more than one component, it has a tearing window — you just haven&#39;t hit the one render that exposes it yet. What&#39;s yours tracking: window size, online status, something else? Tell me in the comments and I&#39;ll tell you if it needs the swap.\u003C\u002Fp>",{"id":232,"type":233},"b35","divider",{"id":235,"html":236,"type":124},"b36","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":238,"html":239,"type":124},"b37","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":241,"type":242,"items":243,"ordered":18},"b38","list",[244,245,246],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","The header says \"You're offline.\" Three pixels below it, the save button is still clickable, sending requests like nothing's wrong. Same `navigator.onLine` value. Same render. Two different answers on screen at the same time.\n\nYou didn't write a new bug. You wrote the hook everyone writes.\n\n```js\nfunction useOnlineStatus() {\n  const [online, setOnline] = useState(navigator.onLine);\n\n  useEffect(() => {\n    const update = () => setOnline(navigator.onLine);\n    window.addEventListener('online', update);\n    window.addEventListener('offline', update);\n    return () => {\n      window.removeEventListener('online', update);\n      window.removeEventListener('offline', update);\n    };\n  }, []);\n\n  return online;\n}\n```\n\nIt worked in every app you shipped before React 18. Then a teammate wraps a route transition in `startTransition` to keep the UI responsive, two components that both call `useOnlineStatus()` briefly disagree, and you spend an afternoon adding `console.log` statements to a bug that reproduces maybe one time in fifteen.\n\n## Why a perfectly normal hook starts lying\n\n`useState` + `useEffect` assumes something React 18 no longer guarantees: that once a component commits, it stays committed until the next explicit update, and that every component re-renders from the same world.\n\nConcurrent rendering breaks that assumption on purpose. React can start rendering a tree, pause partway through to handle something more urgent — a keystroke, a transition — and resume later. If the external value your hook reads (`navigator.onLine`, `window.innerWidth`, a value in `localStorage`, a WebSocket payload) changes in that gap, some components finish rendering with the old value and some with the new one. Nothing crashes. You just get a UI where two places disagree about one fact, for one frame, with no error to catch.\n\nThis has a name: **tearing**. It's not a React bug — it's the cost of letting renders be interruptible, and it only shows up once a render *can* be interrupted.\n\n## The hook was never wrong before — it was just never tested like this\n\nHere's the part that catches people: `useState` + `useEffect` isn't a bad pattern. It's the correct pattern for state React owns. The problem is specifically **external** state — state that lives outside React and changes on its own schedule, which is exactly what \"subscribe to a browser API\" always is.\n\nA `useEffect`-based subscription updates state *after* the store already changed, through a callback, on React's own timing. It has no way to ask, right before this component paints, \"is this still current?\" It just trusts whatever `setOnline` it last received.\n\n## The fix React shipped for exactly this\n\n`useSyncExternalStore` is the hook React 18 added so you stop hand-rolling this. The shape is unusual the first time you see it — three functions, no state:\n\n```js\nimport { useSyncExternalStore } from 'react';\n\nfunction subscribe(callback) {\n  window.addEventListener('online', callback);\n  window.addEventListener('offline', callback);\n  return () => {\n    window.removeEventListener('online', callback);\n    window.removeEventListener('offline', callback);\n  };\n}\n\nfunction getSnapshot() {\n  return navigator.onLine;\n}\n\nfunction useOnlineStatus() {\n  return useSyncExternalStore(subscribe, getSnapshot);\n}\n```\n\n`subscribe` wires up your listener once, same as the `useEffect` before. The difference is `getSnapshot` — a function that reads the *current* value directly, with nothing cached in React state. React calls it on every render, and critically, it re-checks it synchronously right before committing. If the store moved since the last check, React throws that render away and re-renders immediately with the fresh value, instead of letting a stale one paint. That synchronous re-check is the whole fix: it closes the exact gap where tearing happens.\n\nYou're not writing less code than before. You're handing React the one piece of information — \"read it fresh, right now\" — that a `useState` callback can never provide after the fact.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fusesyncexternalstore-tearing-fix\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The part that bites on the server\n\n`useSyncExternalStore` takes an optional third argument, `getServerSnapshot`, and skipping it is the next trap:\n\n```js\nfunction getServerSnapshot() {\n  return true; \u002F\u002F sensible default: assume online during SSR\n}\n\nuseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n```\n\nThere's no `window` on the server, so `getSnapshot` can't run there — `navigator` doesn't exist. If your component can render during SSR and you omit `getServerSnapshot`, React throws rather than guessing. It's not being pedantic: without it, the server-rendered HTML and the first client render would read different realities, and React would rather fail loudly than ship a hydration mismatch silently.\n\nIf your component is client-only (inside a dynamic import with SSR disabled, say), you can skip it. If it ever renders on the server, you can't.\n\n## You've already used this, you just didn't call it\n\nIf you reach for Zustand or react-redux (v8+), you're already running on `useSyncExternalStore` — it's what those libraries' React bindings switched to for exactly this reason. (Jotai, notably, chose not to — its author has written publicly about trading a small tearing risk for behavior that fits Suspense better.) You don't need to rewrite a working store library. You need this hook for the stores you wrote yourself: the `useLocalStorage`, `useMediaQuery`, or `useWebSocketMessage` hook sitting in your `hooks\u002F` folder, quietly subscribing to something outside React with a pattern that was correct until concurrent rendering gave it a seam to fall through.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fusesyncexternalstore-tearing-fix\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nGo find your oldest `useState` + `useEffect`-plus-`addEventListener` hook. If it feeds more than one component, it has a tearing window — you just haven't hit the one render that exposes it yet. What's yours tracking: window size, online status, something else? Tell me in the comments and I'll tell you if it needs the swap.\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":96,"canonical":249,"description":250},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fusesyncexternalstore-tearing-fix","A useState + useEffect hook that subscribes to window size, localStorage, or a WebSocket looks fine until React 18's concurrent rendering shows two parts of your UI two different v","01a14e81-eaa5-700a-a6f6-3ef68535b254",{"id":253,"locked":18},"01a14e81-eac5-77b4-bf02-20dc3aaccf57",[255],{"id":256,"slug":95,"title":257,"_count":258},"01a14e81-eae4-729e-9ec2-22a988e6fdea","useSyncExternalStore and the tearing bug",{"questions":259},6,[261],{"locale":13,"slug":95},{"id":256,"slug":95,"title":257,"_count":263,"questionCount":259},{"questions":259},[],{"items":266,"meta":389},[267,278,297,317,335,353,371],{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":68,"likeCount":19,"commentCount":19,"author":268,"vertical":269,"topic":270,"tags":271,"_count":276,"playground":277,"hasQuiz":17,"hasPlayground":17},{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[272,273,274,275],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":54,"name":55,"color":97},{"assessments":116},{"slug":95},{"id":279,"slug":280,"title":281,"subtitle":97,"excerpt":282,"coverUrl":283,"locale":13,"readingMinutes":284,"publishedAt":285,"viewCount":286,"likeCount":19,"commentCount":19,"author":287,"vertical":288,"topic":289,"tags":290,"_count":295,"playground":296,"hasQuiz":17,"hasPlayground":17},"01a13e2e-d5ae-707f-a6c5-726f2ed01ab0","react-weekly-useeffectevent-stale-closures","React 19.2 useEffectEvent: Why It Won't Fix Your Deps Array","React's useEffectEvent (stable in 19.2) kills stale closures in Effects without a ref trick — but it's not a deps-array shortcut. Guide + cheat sheet.","\u002Fmedia\u002Fcovers\u002Freact-weekly-useeffectevent-stale-closures.png",15,"2026-10-17T12:20:57.469Z",139,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[291,292,293,294],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":58,"name":59,"color":97},{"slug":46,"name":47,"color":97},{"assessments":116},{"slug":280},{"id":298,"slug":299,"title":300,"subtitle":97,"excerpt":301,"coverUrl":302,"locale":13,"readingMinutes":284,"publishedAt":303,"viewCount":304,"likeCount":19,"commentCount":19,"author":305,"vertical":306,"topic":307,"tags":310,"_count":315,"playground":316,"hasQuiz":17,"hasPlayground":17},"01a120b9-d611-74ef-ad15-a267677c3251","nextjs-weekly-image-priority-quality-changes","Next.js 16 Image: priority Deprecated, quality Now Allowlisted","Next.js 16 quietly deprecated Image's priority for preload and restricts quality to an allowlist (default: 75). What breaks, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-image-priority-quality-changes.png","2026-10-13T06:10:29.815Z",429,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":308,"name":309},"nextjs","Nextjs",[311,312,313,314],{"slug":308,"name":309,"color":97},{"slug":74,"name":75,"color":97},{"slug":46,"name":47,"color":97},{"slug":58,"name":59,"color":97},{"assessments":116},{"slug":299},{"id":318,"slug":319,"title":320,"subtitle":97,"excerpt":321,"coverUrl":322,"locale":13,"readingMinutes":84,"publishedAt":323,"viewCount":324,"likeCount":19,"commentCount":19,"author":325,"vertical":326,"topic":327,"tags":328,"_count":333,"playground":334,"hasQuiz":17,"hasPlayground":17},"01a117e0-acb2-740f-9391-db919729cf21","react-weekly-fragment-refs","React 19.3 Fragment Refs: Skip the Wrapper Div for a Ref","React 19.3 Fragment Refs let you ref a sibling group with no wrapper div. Learn the FragmentInstance API, its limits, and get a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Freact-weekly-fragment-refs.png","2026-10-10T12:12:38.989Z",394,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[329,330,331,332],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":58,"name":59,"color":97},{"slug":46,"name":47,"color":97},{"assessments":116},{"slug":319},{"id":336,"slug":337,"title":338,"subtitle":97,"excerpt":339,"coverUrl":340,"locale":13,"readingMinutes":284,"publishedAt":341,"viewCount":342,"likeCount":19,"commentCount":19,"author":343,"vertical":344,"topic":345,"tags":346,"_count":351,"playground":352,"hasQuiz":17,"hasPlayground":17},"01a0f615-d2be-750b-839e-02fd75291b4e","react-weekly-viewtransition-component","React 19.3 ViewTransition: Animate State Without Losing It","React 19.3's ViewTransition component animates state changes, not just page swaps — the mental model, enter\u002Fexit\u002Fupdate\u002Fshare, and a cheat sheet.","\u002Fmedia\u002Fcovers\u002Freact-weekly-viewtransition-component.png","2026-10-03T11:22:26.437Z",459,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[347,348,349,350],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":58,"name":59,"color":97},{"slug":46,"name":47,"color":97},{"assessments":116},{"slug":337},{"id":354,"slug":355,"title":356,"subtitle":97,"excerpt":357,"coverUrl":358,"locale":13,"readingMinutes":100,"publishedAt":359,"viewCount":360,"likeCount":19,"commentCount":19,"author":361,"vertical":362,"topic":363,"tags":364,"_count":369,"playground":370,"hasQuiz":17,"hasPlayground":17},"01a0ec73-247c-7738-84ae-80c9d1d116a8","input-lag-bug-debounce-cant-fix","Your Search Box Lags. Debounce Hides It; `useTransition` Fixes It","A big list re-render was blocking every keystroke — debouncing just moved the freeze. useTransition tells React which update can wait.","\u002Fmedia\u002Fcovers\u002Finput-lag-bug-debounce-cant-fix.png","2026-10-03T11:21:55.564Z",344,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[365,366,367,368],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":66,"name":67,"color":97},{"assessments":116},{"slug":355},{"id":372,"slug":373,"title":374,"subtitle":97,"excerpt":375,"coverUrl":376,"locale":13,"readingMinutes":80,"publishedAt":377,"viewCount":378,"likeCount":19,"commentCount":19,"author":379,"vertical":380,"topic":381,"tags":382,"_count":387,"playground":388,"hasQuiz":17,"hasPlayground":17},"01a0ab57-dda2-74dd-816b-a0ef7f77aeb3","react-weekly-derived-state-bug","React Derived State: Why That useState Is Probably a Bug","React derived state explained: why syncing a computed value into useState with an Effect causes extra renders and drift, and what to write instead.","\u002Fmedia\u002Fcovers\u002Freact-weekly-derived-state-bug.png","2026-09-20T16:27:07.271Z",646,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[383,384,385,386],{"slug":74,"name":75,"color":97},{"slug":50,"name":51,"color":97},{"slug":58,"name":59,"color":97},{"slug":46,"name":47,"color":97},{"assessments":116},{"slug":373},{"page":116,"perPage":390,"total":76,"totalPages":43},7,"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useOnlineStatus\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>online\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setOnline\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useState\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>navigator\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>onLine\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-func\">useEffect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-func\">update\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setOnline\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>navigator\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>onLine\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'online'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> update\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'offline'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> update\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">removeEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'online'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> update\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">removeEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'offline'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> update\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> online;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> useSyncExternalStore \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'react'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">subscribe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>callback\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'online'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> callback\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'offline'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> callback\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">removeEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'online'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> callback\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">removeEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'offline'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> callback\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getSnapshot\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> navigator\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>onLine;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useOnlineStatus\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useSyncExternalStore\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>subscribe\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> getSnapshot\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getServerSnapshot\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F sensible default: assume online during SSR\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">useSyncExternalStore\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>subscribe\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> getSnapshot\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> getServerSnapshot\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":395},[]]