[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-input-lag-bug-debounce-cant-fix":44,"search-suggestions":238,"related-input-lag-bug-debounce-cant-fix":276,"code:jsx:true:w17hua":403,"code:jsx:true:qrhiix":404,"comments-01a0ec73-247c-7738-84ae-80c9d1d116a8":405},[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,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":74,"playground":76,"body":78,"bodyMd":222,"seo":223,"translationGroupId":225,"series":48,"podcastUrl":48,"verticalId":5,"thread":226,"assessments":228,"translations":234,"quiz":236},"01a0ec73-247c-7738-84ae-80c9d1d116a8","input-lag-bug-debounce-cant-fix","Your Search Box Lags. Debounce Hides It; `useTransition` Fixes It",null,"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",5,"2026-10-03T11:21:55.564Z",33,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"react","React",[64,65,68,71],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},"javascript","Javascript",{"slug":69,"name":70,"color":48},"webdev","Webdev",{"slug":72,"name":73,"color":48},"performance","Performance",{"assessments":75},1,{"slug":46,"title":77},"useTransition vs. blocking re-renders — interactive playground",{"blocks":79,"version":75},[80,84,87,90,94,97,103,106,109,112,116,119,122,126,129,133,136,139,142,146,149,152,155,158,161,164,167,170,173,176,179,182,185,188,191,194,197,204,207,210,213,216],{"id":81,"html":82,"type":83},"b1","\u003Cp>You type a letter into a search box filtering 8,000 rows. The letter shows up half a beat after your finger leaves the key. Not broken — just \u003Cem>behind\u003C\u002Fem>, like the page is thinking it over before it lets you see what you typed.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"type":83},"b2","\u003Cp>Everyone&#39;s first move is the same: throw a debounce on it. Wait 300ms after the last keystroke, then filter. It ships. It feels fine in the demo.\u003C\u002Fp>",{"id":88,"html":89,"type":83},"b3","\u003Cp>Then someone types fast, or the list grows, and the lag is back — just relocated. Debouncing didn&#39;t fix the freeze. It hid it behind a timer.\u003C\u002Fp>",{"id":91,"html":92,"text":92,"type":93,"level":31},"b4","The bug, and why debounce only postpones it","heading",{"id":95,"html":96,"type":83},"b5","\u003Cp>Here&#39;s the naive version. An input drives a filter over a big array, and every keystroke re-renders both.\u003C\u002Fp>",{"id":98,"code":99,"type":100,"language":101,"highlight":102},"b6","function ProductSearch({ products }) {\n  const [query, setQuery] = useState(\"\");\n  const filtered = products.filter(p =>\n    p.name.toLowerCase().includes(query.toLowerCase())\n  );\n\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n      \u003CProductList items={filtered} \u002F>\n    \u003C\u002F>\n  );\n}","code","jsx",[],{"id":104,"html":105,"type":83},"b7","\u003Cp>One \u003Ccode>setQuery\u003C\u002Fcode> call does two jobs: it updates the character you just typed, and it triggers React to re-render a list that might be thousands of rows deep. Both happen in the same synchronous render. The browser can&#39;t paint your new keystroke until it&#39;s done computing the whole list, because React doesn&#39;t yet know the two are different kinds of urgent.\u003C\u002Fp>",{"id":107,"html":108,"type":83},"b8","\u003Cp>Debounce doesn&#39;t change that relationship — it just changes \u003Cem>when\u003C\u002Fem> the expensive part runs. Pause for 300ms and start typing again, and the delayed render lands right on top of your next keystrokes, so you get the same freeze, timed differently. And now there&#39;s a second cost: the list visibly lags 300ms behind what you typed, which reads as buggy even when it&#39;s &quot;working as designed.&quot;\u003C\u002Fp>",{"id":110,"html":111,"type":83},"b9","\u003Cp>The real problem was never \u003Cem>when\u003C\u002Fem> the filter runs. It&#39;s that the input update and the list update are forced to share one priority — urgent.\u003C\u002Fp>",{"id":113,"html":114,"text":115,"type":93,"level":31},"b10","What&#39;s actually blocking the keystroke","What's actually blocking the keystroke",{"id":117,"html":118,"type":83},"b11","\u003Cp>Open React DevTools&#39; profiler on that naive version and filter while it&#39;s recording. You&#39;ll see one commit per keystroke, and its duration scales with the list size, not the input. The character you typed was ready to paint instantly — it just had to wait in line behind a re-render it didn&#39;t ask for.\u003C\u002Fp>",{"id":120,"html":121,"type":83},"b12","\u003Cp>That&#39;s the tell: the input isn&#39;t slow. The \u003Cem>scheduling\u003C\u002Fem> is. React treats every update from one event as equally urgent, so re-rendering 8,000 rows blocks a one-character paint.\u003C\u002Fp>",{"id":123,"html":124,"text":125,"type":93,"level":31},"b13","The fix that isn&#39;t a workaround","The fix that isn't a workaround",{"id":127,"html":128,"type":83},"b14","\u003Cp>React 18 shipped a way to say this directly: some updates are urgent (what the user just typed, it must feel instant) and some aren&#39;t (the derived list, which can be a frame or two late without anyone noticing). \u003Ccode>useTransition\u003C\u002Fcode> is that split.\u003C\u002Fp>",{"id":130,"code":131,"type":100,"language":101,"highlight":132},"b15","function ProductSearch({ products }) {\n  const [query, setQuery] = useState(\"\");\n  const [filterQuery, setFilterQuery] = useState(\"\");\n  const [isPending, startTransition] = useTransition();\n\n  function handleChange(e) {\n    const value = e.target.value;\n    setQuery(value); \u002F\u002F urgent: paint the keystroke now\n\n    startTransition(() => {\n      setFilterQuery(value); \u002F\u002F not urgent: React can interrupt this render\n    });\n  }\n\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      \u003Cdiv style={{ opacity: isPending ? 0.6 : 1 }}>\n        \u003CProductList products={products} query={filterQuery} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  );\n}\n\nconst ProductList = memo(function ProductList({ products, query }) {\n  const filtered = products.filter(p =>\n    p.name.toLowerCase().includes(query.toLowerCase())\n  );\n  return \u003Cul>{filtered.map(p => \u003Cli key={p.id}>{p.name}\u003C\u002Fli>)}\u003C\u002Ful>;\n});",[],{"id":134,"html":135,"type":83},"b16","\u003Cp>\u003Ccode>setQuery\u003C\u002Fcode> runs outside the transition, so React paints your keystroke on its normal, fast path — no timer involved. \u003Ccode>setFilterQuery\u003C\u002Fcode> runs inside \u003Ccode>startTransition\u003C\u002Fcode>, which tells React: this update is real, it will happen, but if something more urgent shows up (your next keystroke), interrupt this render and restart it. Two details make that work. The filtering happens \u003Cem>inside\u003C\u002Fem> \u003Ccode>ProductList\u003C\u002Fcode>&#39;s render, not in the handler — \u003Ccode>startTransition\u003C\u002Fcode> calls your callback immediately, so anything expensive you compute there still runs synchronously and blocks the keystroke; only the render it triggers becomes interruptible. And \u003Ccode>ProductList\u003C\u002Fcode> is wrapped in \u003Ccode>memo\u003C\u002Fcode>: the urgent keystroke render re-renders \u003Ccode>ProductSearch\u003C\u002Fcode>, and without \u003Ccode>memo\u003C\u002Fcode> it would drag all 8,000 rows along at urgent priority. \u003Ccode>isPending\u003C\u002Fcode> flips true while a transition is in flight, so the wrapper \u003Ccode>div\u003C\u002Fcode> dims the stale list — it lives outside \u003Ccode>ProductList\u003C\u002Fcode> so the flag doesn&#39;t change the list&#39;s props.\u003C\u002Fp>",{"id":137,"html":138,"type":83},"b17","\u003Cp>No timer to tune. No stale-for-300ms window. If you type three characters in a row, React can abandon the half-finished list render for character one and jump straight to rendering for character three — something debounce can&#39;t do, because once its render starts, it runs to the end.\u003C\u002Fp>",{"id":140,"html":141,"type":83},"b18","\u003Cp>If you&#39;d rather not keep two copies of the query, \u003Ccode>useDeferredValue(query)\u003C\u002Fcode> gives you the same split from a single state variable — and it&#39;s the tool to reach for when the query arrives as a prop you don&#39;t own. Same \u003Ccode>memo\u003C\u002Fcode> requirement either way.\u003C\u002Fp>",{"id":143,"html":144,"text":145,"type":93,"level":31},"b19","Where this earns its keep — and where it doesn&#39;t","Where this earns its keep — and where it doesn't",{"id":147,"html":148,"type":83},"b20","\u003Cp>This is for \u003Cem>derived\u003C\u002Fem> work that&#39;s allowed to lag a frame behind its trigger: search-as-you-type over a big local dataset, a chart that re-renders off a slider, a tab switch that mounts a heavy view. Anywhere &quot;the input feels instant, the result catches up&quot; is an acceptable trade.\u003C\u002Fp>",{"id":150,"html":151,"type":83},"b21","\u003Cp>It&#39;s not a fix for a slow network request — that&#39;s not a scheduling problem, it&#39;s a waiting problem, and \u003Ccode>isPending\u003C\u002Fcode> there is just a nicer loading flag. And it&#39;s not a substitute for \u003Ccode>useMemo\u003C\u002Fcode> or actually reducing the work: if your filter itself is doing something absurd (recompiling a regex every keystroke, say), fix that first. \u003Ccode>useTransition\u003C\u002Fcode> reschedules expensive work; it doesn&#39;t make the work cheaper.\u003C\u002Fp>",{"id":153,"html":154,"type":83},"b22","\u003Cp>Try the naive version and the transition version side by side — same 8,000-row list, same input, and a live counter of how many stale renders got interrupted instead of blocking your typing.\u003C\u002Fp>",{"id":156,"html":157,"type":83},"b23","\u003C!-- playground:start -->",{"id":159,"html":160,"text":160,"type":93,"level":31},"b24","🎮 Try it yourself",{"id":162,"html":163,"type":83},"b25","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finput-lag-bug-debounce-cant-fix\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":165,"html":166,"type":83},"b26","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":168,"html":169,"type":83},"b27","\u003C!-- playground:end -->",{"id":171,"html":172,"type":83},"b28","\u003C!-- quiz:start -->",{"id":174,"html":175,"text":175,"type":93,"level":31},"b29","🧠 Test yourself",{"id":177,"html":178,"type":83},"b30","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finput-lag-bug-debounce-cant-fix\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":180,"html":181,"type":83},"b31","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":183,"html":184,"type":83},"b32","\u003C!-- quiz:end -->",{"id":186,"html":187,"type":83},"b33","\u003Cp>The keystroke was never the slow part. The list update pretending to be as urgent as the keystroke was. Once React knows the difference, it stops making you choose between &quot;fast typing&quot; and &quot;correct results&quot; — you get both: the keystroke in one render, the list in the next.\u003C\u002Fp>",{"id":189,"html":190,"type":83},"b34","\u003Cp>Where else in your app is one \u003Ccode>setState\u003C\u002Fcode> secretly doing two jobs at two different urgencies? I&#39;d bet you can name one from memory.\u003C\u002Fp>",{"id":192,"html":193,"type":83},"b35","\u003C!-- related:start -->",{"id":195,"html":196,"text":196,"type":93,"level":31},"b36","📚 Read next",{"id":198,"type":199,"items":200,"ordered":18},"b37","list",[201,202,203],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle\">Debounce and Throttle in JavaScript: The Complete Guide\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestidlecallback-can-wait-forever\">The Background Task That Waited 40 Seconds for &#39;Idle&#39;\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-web-workers\">Web Workers in JavaScript: The Complete Guide\u003C\u002Fa>",{"id":205,"html":206,"type":83},"b38","\u003C!-- related:end -->",{"id":208,"type":209},"b39","divider",{"id":211,"html":212,"type":83},"b40","\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":214,"html":215,"type":83},"b41","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":217,"type":199,"items":218,"ordered":18},"b42",[219,220,221],"⭐ \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>","You type a letter into a search box filtering 8,000 rows. The letter shows up half a beat after your finger leaves the key. Not broken — just *behind*, like the page is thinking it over before it lets you see what you typed.\n\nEveryone's first move is the same: throw a debounce on it. Wait 300ms after the last keystroke, then filter. It ships. It feels fine in the demo.\n\nThen someone types fast, or the list grows, and the lag is back — just relocated. Debouncing didn't fix the freeze. It hid it behind a timer.\n\n## The bug, and why debounce only postpones it\n\nHere's the naive version. An input drives a filter over a big array, and every keystroke re-renders both.\n\n```jsx\nfunction ProductSearch({ products }) {\n  const [query, setQuery] = useState(\"\");\n  const filtered = products.filter(p =>\n    p.name.toLowerCase().includes(query.toLowerCase())\n  );\n\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n      \u003CProductList items={filtered} \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\nOne `setQuery` call does two jobs: it updates the character you just typed, and it triggers React to re-render a list that might be thousands of rows deep. Both happen in the same synchronous render. The browser can't paint your new keystroke until it's done computing the whole list, because React doesn't yet know the two are different kinds of urgent.\n\nDebounce doesn't change that relationship — it just changes *when* the expensive part runs. Pause for 300ms and start typing again, and the delayed render lands right on top of your next keystrokes, so you get the same freeze, timed differently. And now there's a second cost: the list visibly lags 300ms behind what you typed, which reads as buggy even when it's \"working as designed.\"\n\nThe real problem was never *when* the filter runs. It's that the input update and the list update are forced to share one priority — urgent.\n\n## What's actually blocking the keystroke\n\nOpen React DevTools' profiler on that naive version and filter while it's recording. You'll see one commit per keystroke, and its duration scales with the list size, not the input. The character you typed was ready to paint instantly — it just had to wait in line behind a re-render it didn't ask for.\n\nThat's the tell: the input isn't slow. The *scheduling* is. React treats every update from one event as equally urgent, so re-rendering 8,000 rows blocks a one-character paint.\n\n## The fix that isn't a workaround\n\nReact 18 shipped a way to say this directly: some updates are urgent (what the user just typed, it must feel instant) and some aren't (the derived list, which can be a frame or two late without anyone noticing). `useTransition` is that split.\n\n```jsx\nfunction ProductSearch({ products }) {\n  const [query, setQuery] = useState(\"\");\n  const [filterQuery, setFilterQuery] = useState(\"\");\n  const [isPending, startTransition] = useTransition();\n\n  function handleChange(e) {\n    const value = e.target.value;\n    setQuery(value); \u002F\u002F urgent: paint the keystroke now\n\n    startTransition(() => {\n      setFilterQuery(value); \u002F\u002F not urgent: React can interrupt this render\n    });\n  }\n\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      \u003Cdiv style={{ opacity: isPending ? 0.6 : 1 }}>\n        \u003CProductList products={products} query={filterQuery} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  );\n}\n\nconst ProductList = memo(function ProductList({ products, query }) {\n  const filtered = products.filter(p =>\n    p.name.toLowerCase().includes(query.toLowerCase())\n  );\n  return \u003Cul>{filtered.map(p => \u003Cli key={p.id}>{p.name}\u003C\u002Fli>)}\u003C\u002Ful>;\n});\n```\n\n`setQuery` runs outside the transition, so React paints your keystroke on its normal, fast path — no timer involved. `setFilterQuery` runs inside `startTransition`, which tells React: this update is real, it will happen, but if something more urgent shows up (your next keystroke), interrupt this render and restart it. Two details make that work. The filtering happens *inside* `ProductList`'s render, not in the handler — `startTransition` calls your callback immediately, so anything expensive you compute there still runs synchronously and blocks the keystroke; only the render it triggers becomes interruptible. And `ProductList` is wrapped in `memo`: the urgent keystroke render re-renders `ProductSearch`, and without `memo` it would drag all 8,000 rows along at urgent priority. `isPending` flips true while a transition is in flight, so the wrapper `div` dims the stale list — it lives outside `ProductList` so the flag doesn't change the list's props.\n\nNo timer to tune. No stale-for-300ms window. If you type three characters in a row, React can abandon the half-finished list render for character one and jump straight to rendering for character three — something debounce can't do, because once its render starts, it runs to the end.\n\nIf you'd rather not keep two copies of the query, `useDeferredValue(query)` gives you the same split from a single state variable — and it's the tool to reach for when the query arrives as a prop you don't own. Same `memo` requirement either way.\n\n## Where this earns its keep — and where it doesn't\n\nThis is for *derived* work that's allowed to lag a frame behind its trigger: search-as-you-type over a big local dataset, a chart that re-renders off a slider, a tab switch that mounts a heavy view. Anywhere \"the input feels instant, the result catches up\" is an acceptable trade.\n\nIt's not a fix for a slow network request — that's not a scheduling problem, it's a waiting problem, and `isPending` there is just a nicer loading flag. And it's not a substitute for `useMemo` or actually reducing the work: if your filter itself is doing something absurd (recompiling a regex every keystroke, say), fix that first. `useTransition` reschedules expensive work; it doesn't make the work cheaper.\n\nTry the naive version and the transition version side by side — same 8,000-row list, same input, and a live counter of how many stale renders got interrupted instead of blocking your typing.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finput-lag-bug-debounce-cant-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\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finput-lag-bug-debounce-cant-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\nThe keystroke was never the slow part. The list update pretending to be as urgent as the keystroke was. Once React knows the difference, it stops making you choose between \"fast typing\" and \"correct results\" — you get both: the keystroke in one render, the list in the next.\n\nWhere else in your app is one `setState` secretly doing two jobs at two different urgencies? I'd bet you can name one from memory.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Debounce and Throttle in JavaScript: The Complete Guide](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle)\n- [The Background Task That Waited 40 Seconds for 'Idle'](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestidlecallback-can-wait-forever)\n- [Web Workers in JavaScript: The Complete Guide](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-web-workers)\n\n\u003C!-- related:end -->\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":47,"canonical":224,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finput-lag-bug-debounce-cant-fix","01a0ec73-247d-776e-94df-a6df84caa2f7",{"id":227,"locked":18},"01a0ec73-24a9-7610-86c4-17dcf7583839",[229],{"id":230,"slug":46,"title":231,"_count":232},"01a0ec73-24c9-7353-9430-b7449aabd6ac","Test yourself — the input lag bug and useTransition",{"questions":233},8,[235],{"locale":13,"slug":46},{"id":230,"slug":46,"title":231,"_count":237,"questionCount":233},{"questions":233},[239,241,243,247,251,255,259,261,262,266,269,272],{"slug":69,"name":70,"articles":240},121,{"slug":66,"name":67,"articles":242},100,{"slug":244,"name":245,"articles":246},"frontend","Frontend",78,{"slug":248,"name":249,"articles":250},"tutorial","Tutorial",46,{"slug":252,"name":253,"articles":254},"css","Css",39,{"slug":256,"name":257,"articles":258},"typescript","Typescript",18,{"slug":61,"name":62,"articles":260},15,{"slug":72,"name":73,"articles":260},{"slug":263,"name":264,"articles":265},"browser","Browser",11,{"slug":267,"name":268,"articles":265},"node","Node",{"slug":270,"name":271,"articles":233},"html","Html",{"slug":273,"name":274,"articles":275},"accessibility","Accessibility",7,{"items":277,"meta":402},[278,296,308,327,347,366,384],{"id":279,"slug":280,"title":281,"subtitle":48,"excerpt":282,"coverUrl":283,"locale":13,"readingMinutes":260,"publishedAt":284,"viewCount":285,"likeCount":19,"commentCount":19,"author":286,"vertical":287,"topic":288,"tags":289,"_count":294,"playground":295,"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",38,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[290,291,292,293],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":248,"name":249,"color":48},{"slug":69,"name":70,"color":48},{"assessments":75},{"slug":280},{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":297,"likeCount":19,"commentCount":19,"author":298,"vertical":299,"topic":300,"tags":301,"_count":306,"playground":307,"hasQuiz":17,"hasPlayground":17},34,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[302,303,304,305],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":46},{"id":309,"slug":310,"title":311,"subtitle":48,"excerpt":312,"coverUrl":313,"locale":13,"readingMinutes":314,"publishedAt":315,"viewCount":316,"likeCount":19,"commentCount":19,"author":317,"vertical":318,"topic":319,"tags":320,"_count":325,"playground":326,"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",14,"2026-09-20T16:27:07.271Z",345,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[321,322,323,324],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":248,"name":249,"color":48},{"slug":69,"name":70,"color":48},{"assessments":75},{"slug":310},{"id":328,"slug":329,"title":330,"subtitle":48,"excerpt":331,"coverUrl":332,"locale":13,"readingMinutes":314,"publishedAt":333,"viewCount":334,"likeCount":19,"commentCount":19,"author":335,"vertical":336,"topic":337,"tags":340,"_count":345,"playground":346,"hasQuiz":17,"hasPlayground":17},"01a09181-2f3b-7349-b651-fd9cdda0b931","nextjs-weekly-parallel-intercepting-routes-modals","Next.js Parallel & Intercepting Routes: Modals Done Right","How Next.js parallel routes (@slot) and intercepting routes ((.), (..), (...)) combine to build shareable, refreshable modals — verified against Next.js 16.3.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-parallel-intercepting-routes-modals.png","2026-09-15T17:50:47.218Z",360,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":338,"name":339},"nextjs","Nextjs",[341,342,343,344],{"slug":338,"name":339,"color":48},{"slug":61,"name":62,"color":48},{"slug":69,"name":70,"color":48},{"slug":248,"name":249,"color":48},{"assessments":75},{"slug":329},{"id":348,"slug":349,"title":350,"subtitle":48,"excerpt":351,"coverUrl":352,"locale":13,"readingMinutes":353,"publishedAt":354,"viewCount":355,"likeCount":19,"commentCount":19,"author":356,"vertical":357,"topic":358,"tags":359,"_count":364,"playground":365,"hasQuiz":17,"hasPlayground":17},"01a08734-5c8d-73c5-95d1-e6b4ae366c25","react-weekly-form-actions-pending-state","React Form Actions: useActionState & useFormStatus Guide","How React 19 form Actions manage pending state automatically — useActionState, useFormStatus, and useOptimistic explained with the bug they replace.","\u002Fmedia\u002Fcovers\u002Freact-weekly-form-actions-pending-state.png",13,"2026-09-12T10:23:52.928Z",680,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[360,361,362,363],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":248,"name":249,"color":48},{"slug":69,"name":70,"color":48},{"assessments":75},{"slug":349},{"id":367,"slug":368,"title":369,"subtitle":48,"excerpt":370,"coverUrl":371,"locale":13,"readingMinutes":260,"publishedAt":372,"viewCount":373,"likeCount":19,"commentCount":19,"author":374,"vertical":375,"topic":376,"tags":377,"_count":382,"playground":383,"hasQuiz":17,"hasPlayground":17},"01a06d6c-41f6-77bd-95e3-cdd69a0f72eb","nextjs-weekly-server-actions-mutations-security","Next.js Server Actions: Mutations & Security (Cheat Sheet)","Next.js Server Actions look like plain functions but compile to public POST endpoints. Learn the mutation flow, built-in CSRF checks, and the auth you owe.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-server-actions-mutations-security.png","2026-09-08T10:57:00.975Z",361,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":338,"name":339},[378,379,380,381],{"slug":338,"name":339,"color":48},{"slug":61,"name":62,"color":48},{"slug":69,"name":70,"color":48},{"slug":248,"name":249,"color":48},{"assessments":75},{"slug":368},{"id":385,"slug":386,"title":387,"subtitle":48,"excerpt":388,"coverUrl":389,"locale":13,"readingMinutes":353,"publishedAt":390,"viewCount":391,"likeCount":19,"commentCount":19,"author":392,"vertical":393,"topic":394,"tags":395,"_count":400,"playground":401,"hasQuiz":17,"hasPlayground":17},"01a0632c-7248-73e3-9951-e8c6eaa578c7","react-weekly-compiler-memoization","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","2026-09-05T10:17:49.849Z",382,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[396,397,398,399],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":248,"name":249,"color":48},{"slug":69,"name":70,"color":48},{"assessments":75},{"slug":386},{"page":75,"perPage":275,"total":260,"totalPages":43},"\u003Cdiv class=\"shj shj-lang-jsx shj-multiline\" data-lang=\"jsx\">\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ProductSearch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> products \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>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setQuery\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>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> filtered \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> products\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">filter\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>p \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan>\n    p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toLowerCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">includes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toLowerCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-class\">value\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-class\">onChange\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>e \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setQuery\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ProductList\u003C\u002Fspan> \u003Cspan class=\"shj-class\">items\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>filtered\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-jsx shj-multiline\" data-lang=\"jsx\">\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>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003Cdiv>25\u003C\u002Fdiv>\u003Cdiv>26\u003C\u002Fdiv>\u003Cdiv>27\u003C\u002Fdiv>\u003Cdiv>28\u003C\u002Fdiv>\u003Cdiv>29\u003C\u002Fdiv>\u003Cdiv>30\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ProductSearch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> products \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>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setQuery\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>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>filterQuery\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setFilterQuery\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>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>isPending\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> startTransition\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useTransition\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">handleChange\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value;\n    \u003Cspan class=\"shj-func\">setQuery\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F urgent: paint the keystroke now\n\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">startTransition\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-func\">setFilterQuery\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F not urgent: React can interrupt this render\n\u003C\u002Fspan>    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-class\">value\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-class\">onChange\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>handleChange\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan> \u003Cspan class=\"shj-class\">style\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> opacity\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> isPending \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.6\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ProductList\u003C\u002Fspan> \u003Cspan class=\"shj-class\">products\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>products\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-class\">query\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>filterQuery\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ProductList\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">memo\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ProductList\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> products\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> query \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> filtered \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> products\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">filter\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>p \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan>\n    p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toLowerCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">includes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toLowerCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>filtered\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>p \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan> \u003Cspan class=\"shj-class\">key\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>id\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":406},[]]