[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-usesyncexternalstore-tearing-fix":44,"search-suggestions":60,"quiz-article-usesyncexternalstore-tearing-fix":109,"path-membership-usesyncexternalstore-tearing-fix":275},[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,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a14e81-eae4-729e-9ec2-22a988e6fdea","usesyncexternalstore-tearing-fix","PRACTICE_QUIZ","useSyncExternalStore and the tearing bug","Check what you picked up about subscribing to external state safely under React 18's concurrent rendering.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},6,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,101,105],{"slug":62,"name":63,"articles":64},"webdev","Webdev",151,{"slug":66,"name":67,"articles":68},"javascript","Javascript",124,{"slug":70,"name":71,"articles":72},"frontend","Frontend",88,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",58,{"slug":78,"name":79,"articles":80},"css","Css",45,{"slug":82,"name":83,"articles":84},"performance","Performance",25,{"slug":86,"name":87,"articles":88},"typescript","Typescript",21,{"slug":90,"name":91,"articles":92},"react","React",19,{"slug":94,"name":95,"articles":96},"browser","Browser",14,{"slug":98,"name":99,"articles":100},"node","Node",13,{"slug":102,"name":103,"articles":104},"programming","Programming",12,{"slug":106,"name":107,"articles":108},"accessibility","Accessibility",10,{"id":110,"slug":46,"title":111,"subtitle":52,"excerpt":112,"coverUrl":113,"locale":13,"readingMinutes":114,"publishedAt":115,"viewCount":116,"likeCount":19,"commentCount":19,"author":117,"vertical":122,"topic":123,"tags":124,"_count":129,"playground":131,"body":133,"bodyMd":261,"seo":262,"translationGroupId":265,"series":52,"podcastUrl":52,"verticalId":5,"thread":266,"assessments":268,"translations":271,"quiz":273},"01a14e81-eaa5-700a-a6f6-3be4ffe47c2b","Your Custom Hooks Tear in React 18. `useSyncExternalStore` Fixes It.","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",27,{"id":118,"name":119,"username":120,"avatarUrl":52,"headline":121},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":90,"name":91},[125,126,127,128],{"slug":90,"name":91,"color":52},{"slug":66,"name":67,"color":52},{"slug":62,"name":63,"color":52},{"slug":70,"name":71,"color":52},{"assessments":130},1,{"slug":46,"title":132},"Catch the tear — useSyncExternalStore playground",{"blocks":134,"version":130},[135,139,142,148,151,155,158,161,164,167,170,173,176,179,183,186,189,192,195,198,201,204,207,210,214,217,220,224,227,230,233,236,239,242,245,248,251,254],{"id":136,"html":137,"type":138},"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":140,"html":141,"type":138},"b2","\u003Cp>You didn&#39;t write a new bug. You wrote the hook everyone writes.\u003C\u002Fp>",{"id":143,"code":144,"type":145,"language":146,"highlight":147},"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":149,"html":150,"type":138},"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":152,"html":153,"text":153,"type":154,"level":31},"b5","Why a perfectly normal hook starts lying","heading",{"id":156,"html":157,"type":138},"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":159,"html":160,"type":138},"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":162,"html":163,"type":138},"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":165,"html":166,"text":166,"type":154,"level":31},"b9","The hook was never wrong before — it was just never tested like this",{"id":168,"html":169,"type":138},"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":171,"html":172,"type":138},"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":174,"html":175,"text":175,"type":154,"level":31},"b12","The fix React shipped for exactly this",{"id":177,"html":178,"type":138},"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":180,"code":181,"type":145,"language":146,"highlight":182},"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":184,"html":185,"type":138},"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":187,"html":188,"type":138},"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":190,"html":191,"type":138},"b17","\u003C!-- playground:start -->",{"id":193,"html":194,"text":194,"type":154,"level":31},"b18","🎮 Try it yourself",{"id":196,"html":197,"type":138},"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":199,"html":200,"type":138},"b20","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":202,"html":203,"type":138},"b21","\u003C!-- playground:end -->",{"id":205,"html":206,"text":206,"type":154,"level":31},"b22","The part that bites on the server",{"id":208,"html":209,"type":138},"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":211,"code":212,"type":145,"language":146,"highlight":213},"b24","function getServerSnapshot() {\n  return true; \u002F\u002F sensible default: assume online during SSR\n}\n\nuseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);",[],{"id":215,"html":216,"type":138},"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":218,"html":219,"type":138},"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":221,"html":222,"text":223,"type":154,"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":225,"html":226,"type":138},"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":228,"html":229,"type":138},"b29","\u003C!-- quiz:start -->",{"id":231,"html":232,"text":232,"type":154,"level":31},"b30","🧠 Test yourself",{"id":234,"html":235,"type":138},"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":237,"html":238,"type":138},"b32","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":240,"html":241,"type":138},"b33","\u003C!-- quiz:end -->",{"id":243,"html":244,"type":138},"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":246,"type":247},"b35","divider",{"id":249,"html":250,"type":138},"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":252,"html":253,"type":138},"b37","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":255,"type":256,"items":257,"ordered":18},"b38","list",[258,259,260],"⭐ \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":111,"canonical":263,"description":264},"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":267,"locked":18},"01a14e81-eac5-77b4-bf02-20dc3aaccf57",[269],{"id":45,"slug":46,"title":48,"_count":270},{"questions":51},[272],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":274,"questionCount":51},{"questions":51},[]]