<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ReactChallenges</title>
    <description>The latest articles on DEV Community by ReactChallenges (@reactchallenges).</description>
    <link>https://dev.to/reactchallenges</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F854942%2Fe450946a-3098-4715-8d06-0892ab9ac221.png</url>
      <title>DEV Community: ReactChallenges</title>
      <link>https://dev.to/reactchallenges</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reactchallenges"/>
    <language>en</language>
    <item>
      <title>🚀 New React Challenge: Invoker modal</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Tue, 14 Jul 2026 16:40:48 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-react-challenge-invoker-modal-3000</link>
      <guid>https://dev.to/reactchallenges/new-react-challenge-invoker-modal-3000</guid>
      <description>&lt;p&gt;Tired of writing &lt;code&gt;useState(false)&lt;/code&gt; and &lt;code&gt;onClick&lt;/code&gt; handlers every time you need a modal? The browser already has a declarative API for that.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;Learn how to open and close a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; using only HTML attributes — no React state, no &lt;code&gt;useState&lt;/code&gt;, no event handlers at all. The Invoker Commands API lets buttons control dialogs natively.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 &lt;a href="https://www.reactchallenges.com/challenges/invoker-modal" rel="noopener noreferrer"&gt;https://www.reactchallenges.com/challenges/invoker-modal&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wire the "Contact Us" button to open the dialog using &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;commandfor&lt;/code&gt; attributes&lt;/li&gt;
&lt;li&gt;Wire the "Close" button to close the dialog&lt;/li&gt;
&lt;li&gt;The dialog must start closed&lt;/li&gt;
&lt;li&gt;Keep the existing form fields and &lt;code&gt;data-testid&lt;/code&gt; attributes intact&lt;/li&gt;
&lt;li&gt;Do not remove the TypeScript module augmentation block&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A button uses &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;commandfor&lt;/code&gt; to tell the browser to call &lt;code&gt;showModal()&lt;/code&gt; or &lt;code&gt;close()&lt;/code&gt; — no JavaScript needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useId()&lt;/code&gt; from React is the idiomatic way to generate a unique id for linking button to dialog.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;declare module "react"&lt;/code&gt; block already adds &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;commandfor&lt;/code&gt; to React's button types.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 Tests
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Renders the app title&lt;/li&gt;
&lt;li&gt;Renders the open button with command attributes&lt;/li&gt;
&lt;li&gt;Matches the button commandfor with the dialog id&lt;/li&gt;
&lt;li&gt;Dialog does not have the open attribute initially&lt;/li&gt;
&lt;li&gt;Close button has command close with matching commandfor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This challenge is perfect for developers who want to rely more on the platform and write less JavaScript. You'll learn a native browser API that makes modals simpler, more accessible, and more resilient.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/invoker-modal" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontendchallenge</category>
      <category>frontend</category>
    </item>
    <item>
      <title>🚀 New React Challenge: Tier List</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Mon, 06 Jul 2026 11:44:05 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-react-challenge-tier-list-3n4g</link>
      <guid>https://dev.to/reactchallenges/new-react-challenge-tier-list-3n4g</guid>
      <description>&lt;p&gt;Ever built a drag-and-drop UI with just the native browser API? No &lt;code&gt;react-beautiful-dnd&lt;/code&gt;, no &lt;code&gt;dnd-kit&lt;/code&gt; — just &lt;code&gt;dragStart&lt;/code&gt;, &lt;code&gt;dragOver&lt;/code&gt;, and &lt;code&gt;drop&lt;/code&gt; events. In this challenge, you'll build a Tier List app where users upload images and rank them A through F by dragging, mastering the HTML5 Drag and Drop API along the way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/tier-list" rel="noopener noreferrer"&gt;https://www.reactchallenges.com/challenges/tier-list&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;Build a tier maker app where users upload images and drag them into ranked tiers (A through F) — practicing HTML5 Drag and Drop and file uploads in React.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Render a "Tier List" title at the top of the page&lt;/li&gt;
&lt;li&gt;Render six tier rows labeled A, B, C, D, E, F with the correct background colors&lt;/li&gt;
&lt;li&gt;Include two action buttons: Add (file picker) and Reset (moves all images back)&lt;/li&gt;
&lt;li&gt;Provide a selector area at the bottom where unassigned images appear&lt;/li&gt;
&lt;li&gt;Start with an empty selector and empty tier rows&lt;/li&gt;
&lt;li&gt;File upload: selecting images via file picker adds them to the selector; dragging files from desktop onto the selector also works&lt;/li&gt;
&lt;li&gt;Drag and drop: drag images from selector to tiers, between tiers, and back to the selector&lt;/li&gt;
&lt;li&gt;Highlight the drop zone while dragging over it&lt;/li&gt;
&lt;li&gt;Reset moves all images from tier rows back into the selector area&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've never worked with the native Drag and Drop API before, this is the perfect challenge to get comfortable with it. You'll walk away knowing how to handle file uploads, drag events, and complex UI state — all inside a custom hook.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/tier-list" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>Cognitive Surrender and Why You Should Still Write Code by Hand</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:47:32 +0000</pubDate>
      <link>https://dev.to/reactchallenges/cognitive-surrender-and-why-you-should-still-write-code-by-hand-29ab</link>
      <guid>https://dev.to/reactchallenges/cognitive-surrender-and-why-you-should-still-write-code-by-hand-29ab</guid>
      <description>&lt;p&gt;A study from the University of Pennsylvania introduced a term that should make every developer pause: &lt;strong&gt;cognitive surrender&lt;/strong&gt;. Across 9,500 trials with over 1,300 participants, researchers found that when an AI provided an answer — even a wrong one — people accepted it without scrutiny 73.2% of the time. Not because the answers were convincing. Because the mere presence of a confident AI response was enough to shut down the part of the brain that says &lt;em&gt;"wait, let me think about this."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you write code for a living, you already know where this is going.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/055-simple-pagination" rel="noopener noreferrer"&gt;Simple pagination&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The shortcut that becomes the habit
&lt;/h2&gt;

&lt;p&gt;The researchers describe a shift from two systems of thinking to a third: &lt;strong&gt;artificial cognition&lt;/strong&gt;. Instead of reasoning through a problem, you ask the machine. Instead of verifying, you paste and move on.&lt;/p&gt;

&lt;p&gt;For developers, this is now routine. You need a pagination component. You type a prompt. You get a &lt;code&gt;fetch&lt;/code&gt; inside a &lt;code&gt;useEffect&lt;/code&gt;, some &lt;code&gt;useState&lt;/code&gt;, a loading skeleton. It works. You ship it. You don't think about it again.&lt;/p&gt;

&lt;p&gt;But the study found something revealing: participants with higher &lt;strong&gt;fluid IQ&lt;/strong&gt; — the ability to solve novel problems — were far less likely to be misled by faulty AI. The skill that protected them wasn't knowing more. It was &lt;strong&gt;the habit of reasoning things through&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI belongs (and where it doesn't)
&lt;/h2&gt;

&lt;p&gt;None of this means you should ditch AI. It means you should be intentional about where you deploy it.&lt;/p&gt;

&lt;p&gt;AI is great for &lt;strong&gt;mechanical, repetitive work that requires zero reasoning&lt;/strong&gt;. Generating Storybook stories for every component variant. Writing boilerplate unit tests that follow a predictable pattern. Scaffolding a folder structure. Tasks where the thinking is already done and what's left is typing.&lt;/p&gt;

&lt;p&gt;But when the task involves a decision — &lt;em&gt;how should this state be structured? what does the user need when this errors? should this be optimistic or not?&lt;/em&gt; — that's where you want the friction. That's the muscle you don't want to atrophy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it or lose it
&lt;/h2&gt;

&lt;p&gt;Writing a pagination component is not hard. Fetch data, track the page, handle loading, surface errors. An LLM can do it in three seconds.&lt;/p&gt;

&lt;p&gt;But the point of writing it yourself is not the result. It's the friction.&lt;/p&gt;

&lt;p&gt;When you write a &lt;code&gt;fetch&lt;/code&gt; call manually, you remember how &lt;code&gt;AbortController&lt;/code&gt; works. When you toggle loading and error states by hand, you internalize async UI patterns. When you decide what the retry button looks like, you think about the user. None of that happens when you accept a generated block and move on.&lt;/p&gt;

&lt;p&gt;The experiment design was telling: participants used a modified LLM that gave wrong answers half the time. When the AI was accurate, 93% accepted it. When it was faulty, that only dropped to 80%. Even when the machine was &lt;strong&gt;demonstrably wrong&lt;/strong&gt;, four out of five went along with it. The AI didn't just provide answers — it &lt;strong&gt;lowered the threshold for scrutiny&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not about rejecting AI tools. It's about recognizing that reasoning ability — like any other skill — &lt;strong&gt;atrophies without use&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the muscle alive
&lt;/h2&gt;

&lt;p&gt;The fix is mechanically simple: &lt;strong&gt;practice without the net&lt;/strong&gt;. Not every day, not for every task. But regularly enough that the mental pathways stay warm.&lt;/p&gt;

&lt;p&gt;Pick a problem you could solve with a prompt and solve it manually. Write the pagination from an empty file. Handle the errors. Wire up the loading states. The value is not the code — it's staying fluent enough to know when the AI is wrong, and sharp enough to fix it without asking the AI again.&lt;/p&gt;

&lt;p&gt;Delegate the boilerplate. Protect the thinking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/055-simple-pagination" rel="noopener noreferrer"&gt;Simple pagination&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>🚀 New React Challenge: Simple Pagination</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Sun, 21 Jun 2026 10:35:57 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-react-challenge-simple-pagination-5am4</link>
      <guid>https://dev.to/reactchallenges/new-react-challenge-simple-pagination-5am4</guid>
      <description>&lt;p&gt;In a time where AI writes most of our boilerplate, manually wiring a fetch with loading, error, and pagination states is exactly the kind of thing that slips through the cracks — and exactly what keeps coming up in job interviews.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;A plain fetch with pagination and manual loading and error handling. No libraries, no abstractions — just &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, and a mock API that delays, paginates, and randomly fails. The kind of challenge that keeps your fundamentals sharp.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.reactchallenges.com/challenges/simple-pagination" rel="noopener noreferrer"&gt;https://www.reactchallenges.com/challenges/simple-pagination&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fetch users from &lt;code&gt;https://example/users?page={page}&lt;/code&gt; and display them in a table (ID, Name, Last name). The API returns up to 10 users per page.&lt;/li&gt;
&lt;li&gt;Show a loading skeleton while fetching.&lt;/li&gt;
&lt;li&gt;Display page info: &lt;code&gt;Page {page} of {totalPages} ({count} users)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pagination buttons:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First&lt;/strong&gt;: goes to page 0. Disabled when on page 0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Previous&lt;/strong&gt;: goes to the previous page. Disabled when on page 0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next&lt;/strong&gt;: goes to the next page. Disabled when on the last page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last&lt;/strong&gt;: goes to the last page. Disabled when on the last page.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;All buttons disabled when count is 0 or while loading.&lt;/li&gt;
&lt;li&gt;Display an error message when the request fails.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've been leaning on AI for the boilerplate lately, this one will remind you how much muscle memory matters. Simple fetch, clean state, solid edge cases — exactly what separates a working prototype from interview-ready code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/simple-pagination" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>🚀 New React Challenge: Build a Spreadsheet with Formula Evaluation</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Thu, 11 Jun 2026 09:55:42 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-react-challenge-build-a-spreadsheet-with-formula-evaluation-3hic</link>
      <guid>https://dev.to/reactchallenges/new-react-challenge-build-a-spreadsheet-with-formula-evaluation-3hic</guid>
      <description>&lt;p&gt;You've built todo apps, counters, and forms. But can you handle a grid of 50 cells that reference each other through formulas? This challenge pushes your state management skills into real spreadsheet territory — formula evaluation, two-way cell bindings, and an interface that juggles editing, selection, and keyboard shortcuts all at once.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/spreadsheet" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;Build a spreadsheet with real-time formula evaluation. You'll wire up a 10-row × 5-column grid where cells support basic values and Excel-style formulas (like =A1+B2), column and row selection, and a formula bar that mirrors what you're typing.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Render a spreadsheet with column headers A through E and rows 1 through 10&lt;/li&gt;
&lt;li&gt;Each cell uses an &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; element for the computed value and an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; overlaid for editing&lt;/li&gt;
&lt;li&gt;Click a cell to edit; press Enter or blur to commit the change&lt;/li&gt;
&lt;li&gt;Formulas starting with &lt;code&gt;=&lt;/code&gt; must be evaluated:

&lt;ul&gt;
&lt;li&gt;Arithmetic: &lt;code&gt;=1+1&lt;/code&gt; → &lt;code&gt;2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cell references: A1=&lt;code&gt;5&lt;/code&gt; and B1=&lt;code&gt;=A1+3&lt;/code&gt; → &lt;code&gt;8&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click a column header to select/deselect that column&lt;/li&gt;
&lt;li&gt;Click a row number to select/deselect that row&lt;/li&gt;
&lt;li&gt;Selecting a column deselects any row and vice-versa&lt;/li&gt;
&lt;li&gt;Backspace clears the selected column or row&lt;/li&gt;
&lt;li&gt;Click outside the table deselects everything&lt;/li&gt;
&lt;li&gt;A formula bar (&lt;code&gt;fx&lt;/code&gt;) mirrors the editing cell's value&lt;/li&gt;
&lt;li&gt;Cells must start empty — no default values&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;useState&lt;/code&gt; for cells, selected column, and selected row. No need for useReducer here.&lt;/li&gt;
&lt;li&gt;Each cell uses two overlapping layers: a visible &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; for the computed value and an invisible &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; for the raw formula. Toggle with &lt;code&gt;opacity-0&lt;/code&gt; / &lt;code&gt;opacity-100&lt;/code&gt; so the input stays mounted.&lt;/li&gt;
&lt;li&gt;Evaluate formulas with &lt;code&gt;eval&lt;/code&gt;: generate JS &lt;code&gt;const&lt;/code&gt; declarations from all cell values, wrap them in an IIFE, and evaluate. Recompute &lt;strong&gt;every&lt;/strong&gt; cell on any change — cells can reference each other.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 Tests
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;renders the app title&lt;/li&gt;
&lt;li&gt;renders the spreadsheet with column headers A-E and rows 1-10&lt;/li&gt;
&lt;li&gt;renders cells with initial empty values&lt;/li&gt;
&lt;li&gt;allows editing a cell and displays the new computed value&lt;/li&gt;
&lt;li&gt;evaluates a simple formula&lt;/li&gt;
&lt;li&gt;evaluates a formula with cell references&lt;/li&gt;
&lt;li&gt;re-evaluates formulas when a referenced cell is cleared&lt;/li&gt;
&lt;li&gt;selects a column when clicking the column header&lt;/li&gt;
&lt;li&gt;deselects column when clicking the same header again&lt;/li&gt;
&lt;li&gt;selects a row when clicking the row number&lt;/li&gt;
&lt;li&gt;deselects row when clicking the same row number again&lt;/li&gt;
&lt;li&gt;deselects column when selecting a row and vice-versa&lt;/li&gt;
&lt;li&gt;clears the column when Backspace is pressed with a column selected&lt;/li&gt;
&lt;li&gt;clears the row when Backspace is pressed with a row selected&lt;/li&gt;
&lt;li&gt;deselects column when clicking outside the spreadsheet&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/spreadsheet" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This one will stretch your understanding of React state in ways a simple counter or todo list never could. Formula engines, overlay editing, and cascading recomputation — all with just hooks and vanilla React.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>Your tests pass. Your linter doesn't.</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:02:34 +0000</pubDate>
      <link>https://dev.to/reactchallenges/your-tests-pass-your-linter-doesnt-4mga</link>
      <guid>https://dev.to/reactchallenges/your-tests-pass-your-linter-doesnt-4mga</guid>
      <description>&lt;p&gt;Every React developer has written code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showClearCompleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowClearCompleted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setShowClearCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app works. The tests pass. The feature is shipped. And nobody notices that this code has a bug — not a functional bug, but a &lt;strong&gt;structural one&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silent lie of derived state
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;setShowClearCompleted&lt;/code&gt; inside a &lt;code&gt;useEffect&lt;/code&gt; that watches &lt;code&gt;list&lt;/code&gt; is the React equivalent of buying a notebook, writing down the result of &lt;code&gt;2 + 2&lt;/code&gt;, and then, every time one of the numbers changes, erasing it and writing &lt;code&gt;4&lt;/code&gt; again — instead of just asking "what is &lt;code&gt;2 + 2&lt;/code&gt;?" when you need the answer.&lt;/p&gt;

&lt;p&gt;The value of &lt;code&gt;showClearCompleted&lt;/code&gt; is &lt;strong&gt;derived&lt;/strong&gt; from &lt;code&gt;list&lt;/code&gt;. It is not a separate piece of state. It is a computation over state you already have. Yet the code above treats it as if it were independent — storing it, syncing it, and triggering an extra render to apply it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it yourself: &lt;a href="https://www.reactchallenges.com/challenges/best-practices" rel="noopener noreferrer"&gt;Challenge #53 — Best Practices&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is what the corrected version looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showClearCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No effect. No extra state variable. No cascading render. Just a value computed from state you already own. The component renders, the computation runs, the UI updates — all in a single pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters: cascading renders
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;setState&lt;/code&gt; inside a &lt;code&gt;useEffect&lt;/code&gt;, React has to do &lt;strong&gt;two renders&lt;/strong&gt; instead of one. The first render computes the effect dependencies. The effect fires. It calls &lt;code&gt;setState&lt;/code&gt;. React schedules a second render with the new state.&lt;/p&gt;

&lt;p&gt;In a small component, you won't feel it. In a page with dozens of components, each with their own derived-state effects, the cascade compounds. You get flickers, stale intermediate states, and a performance profile that is needlessly heavy for no reason at all.&lt;/p&gt;

&lt;p&gt;And the worst part? If you only test the output — the DOM, the rendered text, the visible behavior — &lt;strong&gt;you will never catch this&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most platforms don't check. We do.
&lt;/h2&gt;

&lt;p&gt;Go to any coding challenge site. Pick a React challenge. Write the version with &lt;code&gt;useEffect&lt;/code&gt;. Run the tests. They pass. Congratulations, you solved it.&lt;/p&gt;

&lt;p&gt;But did you write &lt;em&gt;good React&lt;/em&gt;? The answer depends on what "good" means. If it means "the tests pass," then yes. If it means "idiomatic, performant, maintainable React that your coworkers won't silently judge during code review," then no.&lt;/p&gt;

&lt;p&gt;Challenge #53 on React Challenges is different. When you hit &lt;strong&gt;Run Tests&lt;/strong&gt;, two things happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your code runs against a suite of behavioral tests (does the todo list add, delete, toggle, and clear items?)&lt;/li&gt;
&lt;li&gt;Your code is checked against an &lt;strong&gt;ESLint configuration&lt;/strong&gt; that enforces React best practices&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you use &lt;code&gt;useEffect&lt;/code&gt; to sync derived state, the linter catches it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ✗ App.tsx:29:5  error
   react-hooks/set-state-in-effect
   Calling setState synchronously within an effect causes cascading renders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the challenge is &lt;strong&gt;not considered complete&lt;/strong&gt; until all lint rules pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test-passing trap
&lt;/h2&gt;

&lt;p&gt;There is a quiet assumption in the coding challenge world: if the tests pass, you are done. This assumption trains developers into habits that are fine for a green checkmark and dangerous for a production codebase.&lt;/p&gt;

&lt;p&gt;You can write a component that works and is wrong. You can render the correct UI with code that silently burns through render cycles, hides derived data behind state machines, and uses effects as event handlers because the mental model of React's render cycle never fully clicked.&lt;/p&gt;

&lt;p&gt;Most platforms let you walk away with the green checkmark and a false sense of mastery. We wanted to build one that tells you the second thing: &lt;strong&gt;yes, it works — but it is not right yet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And for that, you need a linter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it yourself: &lt;a href="https://www.reactchallenges.com/challenges/best-practices" rel="noopener noreferrer"&gt;Challenge #53 — Best Practices&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>testing</category>
      <category>useeffect</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🚀 New React Challenge: Best Practices</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Mon, 08 Jun 2026 11:25:53 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-challenge-best-practices-280e</link>
      <guid>https://dev.to/reactchallenges/new-challenge-best-practices-280e</guid>
      <description>&lt;p&gt;Your todo list passes all tests. You ship it. But here's the thing no one tells you about interviews — reviewers don't just check that your code works. They look for the 3 anti-patterns hiding in plain sight. This challenge puts ESLint in the reviewer's seat and asks you to clean up the code without breaking a single test.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 &lt;a href="https://www.reactchallenges.com/challenges/best-practices" rel="noopener noreferrer"&gt;https://www.reactchallenges.com/challenges/best-practices&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;In a real technical interview, solving the logic is only half the battle. Reviewers are trained to spot lazy &lt;code&gt;useState&lt;/code&gt; initializers, derived state hidden inside &lt;code&gt;useEffect&lt;/code&gt;, and impure calls during render. This challenge gives you a working todo list and surfaces exactly those violations through automated linting. Fix all 3 warnings without changing how the app behaves.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fix all ESLint warnings without changing app behavior&lt;/li&gt;
&lt;li&gt;All 6 tests must keep passing after your changes&lt;/li&gt;
&lt;li&gt;Resolve these 3 violations:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@eslint-react/use-state&lt;/code&gt; — pass a lazy initializer instead of calling the function eagerly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-hooks/set-state-in-effect&lt;/code&gt; — derive &lt;code&gt;showClearCompleted&lt;/code&gt; directly from state, skip the &lt;code&gt;useEffect&lt;/code&gt; workaround&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@eslint-react/purity&lt;/code&gt; — move &lt;code&gt;new Date()&lt;/code&gt; out of the render path&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Three small fixes, three big "aha" moments. Whether you're prepping for React interviews or just want to stop shipping code that silently wastes renders, this one will sharpen the way you read your own components. No new features — just cleaner, faster React.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/best-practices" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>What useOptimistic Actually Saves You</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Tue, 26 May 2026 17:20:19 +0000</pubDate>
      <link>https://dev.to/reactchallenges/what-useoptimistic-actually-saves-you-1hm</link>
      <guid>https://dev.to/reactchallenges/what-useoptimistic-actually-saves-you-1hm</guid>
      <description>&lt;p&gt;A checkbox toggle should feel instant. But when that toggle needs to persist to a server, you face a choice: wait for the response and feel sluggish, or update immediately and handle the fallout. The second option — optimistic UI — is better for users, but the manual implementation adds state, flags, and try/catch blocks that pile up fast. For a simple checkbox the difference is small — a few lines. For anything more complex, the gap widens quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/use-optimistic" rel="noopener noreferrer"&gt;useOptimistic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The manual way: state + pending + rollback
&lt;/h2&gt;

&lt;p&gt;Here's a typical TODO checkbox that optimistically updates the UI before the server responds. No libraries — just React's built-in hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onToggle&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setChecked&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPending&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleToggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setChecked&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setPending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;onToggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setChecked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setPending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleToggle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;26 lines for one checkbox. The &lt;code&gt;try/catch&lt;/code&gt; handles a server error, but what if the component unmounts while the request is in flight? What if the parent updates &lt;code&gt;todo.completed&lt;/code&gt; from a different source while your toggle is pending — the local &lt;code&gt;checked&lt;/code&gt; state and the prop drift apart, and the catch handler restores a stale value. Each edge case adds more state and more branches.&lt;/p&gt;

&lt;h2&gt;
  
  
  With useOptimistic
&lt;/h2&gt;

&lt;p&gt;Now the same feature with &lt;code&gt;useOptimistic&lt;/code&gt; and &lt;code&gt;startTransition&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onToggle&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;optimisticChecked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addOptimistic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useOptimistic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleToggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;addOptimistic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;onToggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;optimisticChecked&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleToggle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;23 lines. Not a dramatic difference in raw line count. The savings aren't in how many lines you type — they're in what you no longer have to think about.&lt;/p&gt;

&lt;p&gt;Here's why: &lt;code&gt;startTransition&lt;/code&gt; wraps the async work in a React Transition. When the transition ends (on success or error), React re-renders the component. At that point, &lt;code&gt;useOptimistic&lt;/code&gt; simply renders whatever &lt;code&gt;todo.completed&lt;/code&gt; is. If the parent updated it — success, the checkbox stays checked. If the parent didn't update it — failure, the checkbox goes back to how it was. The rollback isn't "built-in error handling." It's just a consequence of the passthrough prop not changing.&lt;/p&gt;

&lt;p&gt;The manual version needs &lt;code&gt;try/catch&lt;/code&gt; for a different reason: it has a separate &lt;code&gt;pending&lt;/code&gt; flag and a separate &lt;code&gt;checked&lt;/code&gt; state. If &lt;code&gt;onToggle&lt;/code&gt; throws, both are stuck in the wrong value unless you manually reset them. With &lt;code&gt;useOptimistic&lt;/code&gt;, there's nothing to reset — the prop is the only source of truth.&lt;/p&gt;

&lt;p&gt;For a checkbox, it's a few lines. For anything more complex — adding to a list, toggling related fields, or managing multiple optimistic values — the manual approach balloons while the &lt;code&gt;useOptimistic&lt;/code&gt; version barely grows. That's where the real savings live.&lt;/p&gt;

&lt;p&gt;This doesn't mean you can skip error handling entirely. The UI reverts on its own, but you still want to show an error toast, log the failure, or offer a retry — all of which happen outside the optimistic state logic. &lt;code&gt;useOptimistic&lt;/code&gt; handles what the checkbox shows; you still handle what the user should know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep complexity at the edge
&lt;/h2&gt;

&lt;p&gt;There's a pattern here that goes beyond this checkbox.&lt;/p&gt;

&lt;p&gt;Any time your code has to deal with something external — a server response, a user action, an API call — you have two options. You can let that uncertainty spread through your functions and components, or you can contain it at the boundary where it enters.&lt;/p&gt;

&lt;p&gt;The Zod rule &lt;strong&gt;validate at system boundaries&lt;/strong&gt; does exactly this for server-side data. Validate the request body at the endpoint, and every function downstream receives clean, typed data. No more checking "is this field a string?" ten layers deep.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useOptimistic&lt;/code&gt; does the same thing for UI state. The user clicks, and instead of spraying &lt;code&gt;pending&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; flags across your component, you declare the optimistic change right where the user acted — inside &lt;code&gt;startTransition&lt;/code&gt;. The hook keeps the UI in sync with the prop. Everything downstream just renders.&lt;/p&gt;

&lt;p&gt;One boundary, one place to think about it, one thing to debug.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/use-optimistic" rel="noopener noreferrer"&gt;useOptimistic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>useoptimistic</category>
      <category>react19</category>
    </item>
    <item>
      <title>🚀 New React Challenge: Instant UI with useOptimistic</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Sat, 23 May 2026 10:37:07 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-react-challenge-instant-ui-with-useoptimistic-16lc</link>
      <guid>https://dev.to/reactchallenges/new-react-challenge-instant-ui-with-useoptimistic-16lc</guid>
      <description>&lt;p&gt;Ever clicked a like button and watched it stall for a second while the server thinks about it? That tiny delay makes your app feel sluggish. React 19 ships &lt;code&gt;useOptimistic&lt;/code&gt; to fix exactly that — the UI changes &lt;em&gt;before&lt;/em&gt; the server responds, and snaps back automatically if something goes wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/use-optimistic" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;Learn React's &lt;code&gt;useOptimistic&lt;/code&gt; hook by building instant heart toggles. Click the heart, see it turn red immediately, and let React handle the server in the background. If the request fails, the heart reverts on its own — no manual rollback code.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Render two cards side by side: one labeled "Successful toggle" and another "Error toggle".&lt;/li&gt;
&lt;li&gt;Both cards start with the heart in a neutral (not liked) state.&lt;/li&gt;
&lt;li&gt;Clicking the heart on the &lt;strong&gt;successful toggle&lt;/strong&gt; card must:

&lt;ul&gt;
&lt;li&gt;Turn the heart red &lt;strong&gt;immediately&lt;/strong&gt;, before the server responds.&lt;/li&gt;
&lt;li&gt;Keep the heart red after the server confirms the change.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Clicking the heart on the &lt;strong&gt;error toggle&lt;/strong&gt; card must:

&lt;ul&gt;
&lt;li&gt;Turn the heart red &lt;strong&gt;immediately&lt;/strong&gt;, before the server responds.&lt;/li&gt;
&lt;li&gt;Revert the heart to neutral after the server responds with an error.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Use the &lt;code&gt;useOptimistic&lt;/code&gt; hook together with &lt;code&gt;startTransition&lt;/code&gt; to manage the optimistic state.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;addOptimistic&lt;/code&gt; must be called inside &lt;code&gt;startTransition&lt;/code&gt; — otherwise React warns even though the UI still updates.&lt;/li&gt;
&lt;li&gt;The passthrough state only needs to be a &lt;code&gt;boolean&lt;/code&gt; (just the like value). The rest of &lt;code&gt;data&lt;/code&gt; can stay static.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toggleFavoriteError&lt;/code&gt; always throws. A try/catch with &lt;code&gt;console.error&lt;/code&gt; prevents the red error overlay in dev without affecting the optimistic revert.&lt;/li&gt;
&lt;li&gt;The data-testid attributes in the starter are already wired to the tests — don't rename or remove them.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 Tests
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;renders the app title&lt;/li&gt;
&lt;li&gt;renders both cards with labels&lt;/li&gt;
&lt;li&gt;starts with hearts not liked&lt;/li&gt;
&lt;li&gt;optimistically toggles the heart immediately and persists on success&lt;/li&gt;
&lt;li&gt;reverts the heart on server error&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you've been writing &lt;code&gt;setState(optimisticValue)&lt;/code&gt;, then &lt;code&gt;try/catch&lt;/code&gt; with manual rollbacks, &lt;code&gt;useOptimistic&lt;/code&gt; deletes all that boilerplate. Give it a try and feel how snappy your UI can be.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reactchallenges.com/challenges/use-optimistic" rel="noopener noreferrer"&gt;🔥 Start the Challenge Now&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>🚀 New Challenge: Shopping Cart with Redux</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Wed, 13 May 2026 16:11:34 +0000</pubDate>
      <link>https://dev.to/reactchallenges/new-challenge-shopping-cart-with-redux-1d7k</link>
      <guid>https://dev.to/reactchallenges/new-challenge-shopping-cart-with-redux-1d7k</guid>
      <description>&lt;p&gt;Redux is one of those libraries that makes perfect sense when you read the docs — but feels completely different when you're the one writing the slice, dispatching actions, and watching components react to state changes. Theory gets you started; building gets you fluent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ready to code? Try the challenge: &lt;a href="https://www.reactchallenges.com/challenges/shopping-cart-with-redux" rel="noopener noreferrer"&gt;Shopping cart with Redux&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's be honest: for most React applications, Redux is overengineering. With Context and &lt;code&gt;useReducer&lt;/code&gt;, you can build a global state solution that's just as capable — less boilerplate, fewer dependencies. But the job market has its own rules. Many teams still use Redux, many codebases were built with it, and many job descriptions list it as a requirement. Knowing Redux isn't about ideology — it's about being ready for the code you'll actually encounter.&lt;/p&gt;

&lt;p&gt;This challenge puts you in the driver's seat of a real Redux workflow: you'll build a fully functional shopping cart powered by Redux Toolkit, from the slice logic all the way up to a multi-component React UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;You'll create a complete shopping cart interface with the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add products to the cart with quantity tracking&lt;/li&gt;
&lt;li&gt;Remove products individually&lt;/li&gt;
&lt;li&gt;Update product quantities in real time&lt;/li&gt;
&lt;li&gt;Toggle the cart's open/closed state from a global slice&lt;/li&gt;
&lt;li&gt;Display a running total derived from the cart state&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Ready to code? Try the challenge: &lt;a href="https://www.reactchallenges.com/challenges/shopping-cart-with-redux" rel="noopener noreferrer"&gt;Shopping cart with Redux&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Looking for more React challenges? Check out all the available challenges at &lt;a href="https://www.reactchallenges.com" rel="noopener noreferrer"&gt;reactchallenges.com&lt;/a&gt; — from beginner to advanced, with real test suites that validate your solutions.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>frontendchallenge</category>
    </item>
    <item>
      <title>Debounce vs Throttle: When to Use Each</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Tue, 12 May 2026 07:49:22 +0000</pubDate>
      <link>https://dev.to/reactchallenges/debounce-vs-throttle-when-to-use-each-and-why-it-matters-55l0</link>
      <guid>https://dev.to/reactchallenges/debounce-vs-throttle-when-to-use-each-and-why-it-matters-55l0</guid>
      <description>&lt;p&gt;Most React developers have faced the problem: a search input that fires an API call on &lt;strong&gt;every single keystroke&lt;/strong&gt;, or a scroll handler that runs hundreds of times per second. It wastes bandwidth, clogs the main thread, and creates a terrible user experience. The fix is straightforward — you need to &lt;strong&gt;control how often your functions run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;debounce&lt;/strong&gt; and &lt;strong&gt;throttle&lt;/strong&gt; come in. They're two related but distinct techniques for limiting function execution frequency. Knowing which one to pick saves you from subtle bugs and performance headaches.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/use-debounce-vs-use-throttle" rel="noopener noreferrer"&gt;useDebounce vs useThrottle&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Debounce: wait until things settle
&lt;/h2&gt;

&lt;p&gt;Debounce delays execution until a certain amount of time has passed &lt;strong&gt;without any new calls&lt;/strong&gt;. Think of an elevator door — it starts closing, but if someone walks in, it reopens and waits again. The door only actually closes once people stop arriving.&lt;/p&gt;

&lt;p&gt;In code, debounce is the go-to choice for &lt;strong&gt;search inputs&lt;/strong&gt;. You don't want to query your API on &lt;code&gt;"r"&lt;/code&gt;, &lt;code&gt;"re"&lt;/code&gt;, &lt;code&gt;"rea"&lt;/code&gt;, &lt;code&gt;"reac"&lt;/code&gt;, &lt;code&gt;"react"&lt;/code&gt;. You want to wait until the user &lt;strong&gt;stops typing&lt;/strong&gt; and then make one request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useDebounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;debouncedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDebouncedValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setDebouncedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;debouncedValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time &lt;code&gt;value&lt;/code&gt; changes, the timer resets. Only when the value stops changing for &lt;code&gt;delay&lt;/code&gt; milliseconds does the debounced value update. The cleanup function (&lt;code&gt;clearTimeout&lt;/code&gt;) is critical — without it, stale timers accumulate and you get unexpected updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other debounce use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize event handlers (wait until the user finishes resizing the browser)&lt;/li&gt;
&lt;li&gt;Autosave in text editors (save after the user pauses typing)&lt;/li&gt;
&lt;li&gt;Form validation triggered on &lt;code&gt;onChange&lt;/code&gt; (validate once, not on every keystroke)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Throttle: guarantee a maximum rate
&lt;/h2&gt;

&lt;p&gt;Throttle works differently. Instead of waiting for things to settle, it guarantees execution &lt;strong&gt;at most once&lt;/strong&gt; within a fixed time window. Think of a revolving door — it lets people through at a controlled, constant rate no matter how many are trying to push in.&lt;/p&gt;

&lt;p&gt;Scroll handlers are the classic example. When the user scrolls, the browser fires &lt;code&gt;scroll&lt;/code&gt; events at an incredibly high rate — easily 60+ times per second. You rarely need to run logic at that frequency. Throttling to once every 100ms or 200ms keeps the UI smooth while still updating on time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useThrottle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;throttledValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setThrottledValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastRan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastRan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setThrottledValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;lastRan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setThrottledValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;lastRan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;throttledValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference from debounce: throttle tracks the &lt;strong&gt;last execution time&lt;/strong&gt; and allows a new execution only if enough time has elapsed. It doesn't care whether values are still changing — it just enforces a speed limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other throttle use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracking mouse position for tooltips or custom cursors&lt;/li&gt;
&lt;li&gt;Handling &lt;code&gt;infinite scroll&lt;/code&gt; pagination (trigger a fetch at most once every N milliseconds)&lt;/li&gt;
&lt;li&gt;Rate-limiting rapid button clicks (prevent double-submits without disabling the button)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debounce vs Throttle: side by side
&lt;/h2&gt;

&lt;p&gt;The difference becomes clear when you visualize how each behaves with rapid input:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;Analogy&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debounce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Executes once after activity stops&lt;/td&gt;
&lt;td&gt;Elevator door&lt;/td&gt;
&lt;td&gt;Search inputs, autosave, validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throttle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Executes at a fixed maximum rate&lt;/td&gt;
&lt;td&gt;Revolving door&lt;/td&gt;
&lt;td&gt;Scroll, resize, mouse tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The same input sequence produces different results:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine firing an event 100 times in 1 second. With a debounce of 300ms, the handler runs &lt;strong&gt;once&lt;/strong&gt; (after the last event, provided 300ms passes with no new events). With a throttle of 300ms, the handler runs &lt;strong&gt;3-4 times&lt;/strong&gt; (roughly once every 300ms during that second).&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Using debounce when you need throttle.&lt;/strong&gt; A scroll-driven animation debounced at 200ms will look broken — it won't fire at all while the user is scrolling, then it'll jump awkwardly when they stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using throttle when you need debounce.&lt;/strong&gt; A search input throttled at 300ms sends unnecessary intermediate requests (every 300ms instead of just the final query). The user's network and your API both pay the price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not cleaning up timeouts in React.&lt;/strong&gt; This is the most frequent bug. If your component unmounts while a timeout is pending, &lt;code&gt;setState&lt;/code&gt; on an unmounted component triggers React warnings (and in older versions, actual memory leaks). The &lt;code&gt;useEffect&lt;/code&gt; cleanup function prevents this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting &lt;code&gt;useRef&lt;/code&gt; for mutable values.&lt;/strong&gt; When implementing throttle, you need to track &lt;code&gt;lastRan&lt;/code&gt; across renders. Plain variables reset on every render, and &lt;code&gt;useState&lt;/code&gt; causes unnecessary re-renders. &lt;code&gt;useRef&lt;/code&gt; is the right tool — it persists across renders without triggering updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why custom hooks for this?
&lt;/h2&gt;

&lt;p&gt;You can absolutely import &lt;code&gt;debounce&lt;/code&gt; and &lt;code&gt;throttle&lt;/code&gt; from lodash or write them as plain utility functions. But wrapping them in hooks gives you something powerful: &lt;strong&gt;the delay only affects the debounced/throttled value, not the original source of truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your component keeps an up-to-date &lt;code&gt;value&lt;/code&gt; at all times (for immediate UI feedback like showing typed characters in an input), while the debounced/throttled version is used only where needed (for API calls). This separation of concerns is clean, explicit, and prevents the timing logic from leaking across your component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;Most implementations of &lt;code&gt;useDebounce&lt;/code&gt; and &lt;code&gt;useThrottle&lt;/code&gt; power through the happy path. Real-world usage introduces edge cases worth thinking through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What happens when the delay changes dynamically?&lt;/strong&gt; If a user adjusts a slider controlling debounce delay, does your hook handle it gracefully or does it skip updates?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What if &lt;code&gt;value&lt;/code&gt; is an object?&lt;/strong&gt; Reference equality means &lt;code&gt;{ query: "react" }&lt;/code&gt; will always be "new" — you might want deep comparison or a specific key to watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Should the initial value fire immediately?&lt;/strong&gt; Some debounce implementations let you pass a &lt;code&gt;leading&lt;/code&gt; option to trigger on the first call instead of waiting. Tailwind's &lt;code&gt;prefers-reduced-motion&lt;/code&gt; equivalent is worth considering for accessibility too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are exactly the kind of subtleties that turn a 10-line hook into a 50-line one — and the kind of thing that comes up in code reviews.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/use-debounce-vs-use-throttle" rel="noopener noreferrer"&gt;useDebounce vs useThrottle&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Fetching Data Sequentially When It Could Be Parallel</title>
      <dc:creator>ReactChallenges</dc:creator>
      <pubDate>Sun, 10 May 2026 11:22:19 +0000</pubDate>
      <link>https://dev.to/reactchallenges/stop-fetching-data-sequentially-when-it-could-be-parallel-15jh</link>
      <guid>https://dev.to/reactchallenges/stop-fetching-data-sequentially-when-it-could-be-parallel-15jh</guid>
      <description>&lt;p&gt;Picture this: you fetch a list of items from an API, and for each item you need extra data from a different endpoint. The natural instinct is to loop through the items and fire a request for each one — maybe inside a &lt;code&gt;useEffect&lt;/code&gt;, maybe with an &lt;code&gt;await&lt;/code&gt; inside a &lt;code&gt;.map&lt;/code&gt;. The code works. The data shows up. But if you look at the network tab, every request waits for the one before it, and your loading spinner sits there for seconds longer than it should.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;sequential fetching of independent data&lt;/strong&gt;, and it is one of the easiest performance wins you can grab in any frontend app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/rick-and-morty-double-fetching" rel="noopener noreferrer"&gt;Rick &amp;amp; Morty: Double Fetching&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The trap
&lt;/h2&gt;

&lt;p&gt;Imagine you are building a page that displays characters from the Rick &amp;amp; Morty API. The characters come from &lt;code&gt;https://rickandmortyapi.com/api/character/?page=1&lt;/code&gt;, and for each character you also want to show the title of their first episode. That episode title lives at &lt;code&gt;character.episode[0]&lt;/code&gt; — a separate URL.&lt;/p&gt;

&lt;p&gt;Here is the code that feels right but is wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchCharacters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://rickandmortyapi.com/api/character/?page=1`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;character&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodeRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;episodeRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;episodeTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;episodeData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads like a recipe: get all characters, then for each character, get their episode. Step by step. But here is the problem: &lt;strong&gt;episode 2's data does not depend on episode 1's data&lt;/strong&gt;. There is zero reason for request 2 to wait for request 1 to finish. And yet, with &lt;code&gt;await&lt;/code&gt; inside a &lt;code&gt;for&lt;/code&gt; loop, that is exactly what happens.&lt;/p&gt;

&lt;p&gt;If you have 20 characters and each episode request takes 200ms, the episode fetching alone takes &lt;strong&gt;4 seconds&lt;/strong&gt; — 20 requests × 200ms — when it could take 200ms total.&lt;/p&gt;

&lt;p&gt;The same trap shows up in other forms. A &lt;code&gt;useEffect&lt;/code&gt; that awaits one fetch, then another, then another. Three independent requests that could fire simultaneously, queued up as if they share a dependency. Every &lt;code&gt;await&lt;/code&gt; that isn't necessary is a &lt;strong&gt;waterfall&lt;/strong&gt; you are paying for with user time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: Promise.all
&lt;/h2&gt;

&lt;p&gt;The solution is &lt;code&gt;Promise.all&lt;/code&gt;. Instead of awaiting episode requests one by one, fire all of them at once and wait for the group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchCharacters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://rickandmortyapi.com/api/character/?page=1`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodePromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;episodePromises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;episodeTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;episodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all 20 episode requests leave the browser at roughly the same time. The total wait is the slowest individual request, not the sum of all of them. For 20 episodes at 200ms each, you go from 4 seconds to about 200ms. That is not a micro-optimization — that is the difference between a sluggish app and a fast one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Promise.all&lt;/code&gt; fails fast: if one request rejects, the whole thing rejects. In a real app you probably don't want a single failed episode to crash the entire character list. A small adjustment handles that gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;charactersWithEpisodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;episodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;episodeTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Promise.allSettled&lt;/code&gt; waits for all promises to complete — whether they succeed or fail — and gives you back the result of each one. One broken request won't take down the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recognizing the pattern in the wild
&lt;/h2&gt;

&lt;p&gt;Sequential fetching rarely announces itself with a visible &lt;code&gt;for&lt;/code&gt; loop. Here are the places it hides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chained await in effects.&lt;/strong&gt; A &lt;code&gt;useEffect&lt;/code&gt; that looks like this is a waterfall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;        &lt;span class="c1"&gt;// waits&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// waits for user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// waits for posts&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;settings&lt;/code&gt; don't depend on &lt;code&gt;user&lt;/code&gt;, fetch them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSettings&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nested .then() instead of Promise.all.&lt;/strong&gt; This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/reviews/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// one by one, sequentially&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/reviews/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Accidental serial in server components.&lt;/strong&gt; A React Server Component that does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM orders WHERE user_id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the database can handle concurrent queries, fire them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM orders WHERE user_id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When sequential is actually correct
&lt;/h2&gt;

&lt;p&gt;Not everything should be parallel. The rule is simple: &lt;strong&gt;if request B needs data from request A, await A first.&lt;/strong&gt; A search endpoint that returns results from a query, followed by a detail endpoint that needs the ID from the first result — that chain is necessary.&lt;/p&gt;

&lt;p&gt;Also, some APIs have rate limits. Sending 100 parallel requests to a third-party API that caps you at 10 per second will get you blocked. In those cases, &lt;strong&gt;batch your requests&lt;/strong&gt; or use a concurrency limiter. But don't pre-optimize for rate limits you don't have. Most internal APIs, public APIs like Rick &amp;amp; Morty, and your own backend can handle parallel requests just fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing: loading state
&lt;/h2&gt;

&lt;p&gt;With sequential fetching, you can naively show a loading indicator and it sort of works — the spinner just stays longer than it should. With parallel fetching, you want to be more deliberate about what "loading" means. Show a skeleton or a list of placeholders while the character list loads. Then, as episode titles arrive, fill them in gradually or all at once with &lt;code&gt;Promise.all&lt;/code&gt;. The user sees content faster, even if some details take an extra moment.&lt;/p&gt;

&lt;p&gt;The important shift is &lt;strong&gt;keeping the character list and the episode titles in a single state update&lt;/strong&gt;. If you set characters first and then update each one with its episode title, you trigger unnecessary re-renders and risk showing incomplete data. Resolve everything together, then set state once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCharacters&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchCharacters&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// fetches + episodes in parallel&lt;/span&gt;
  &lt;span class="nf"&gt;setCharacters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                        &lt;span class="c1"&gt;// single state update&lt;/span&gt;
  &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might seem obvious, but it is easy to mess up when the fetches are split across different functions or hooks. &lt;strong&gt;Resolve all independent data, then commit to state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The performance cost of sequential fetching is invisible during development — local APIs respond in milliseconds. But on a slow 3G connection or a cold serverless function start, those waterfalls add up into seconds. Recognizing when data doesn't depend on other data, and fetching it accordingly, is one of the highest-leverage skills you can build as a frontend developer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Try it in practice: &lt;a href="https://www.reactchallenges.com/challenges/rick-and-morty-double-fetching" rel="noopener noreferrer"&gt;Rick &amp;amp; Morty: Double Fetching&lt;/a&gt; — fix sequential episode fetching and see the difference in the network tab.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
