<?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: Sarav</title>
    <description>The latest articles on DEV Community by Sarav (@sarav).</description>
    <link>https://dev.to/sarav</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%2F4229203%2F4c9a133d-42ad-40df-bdb0-2c609e17a495.jpg</url>
      <title>DEV Community: Sarav</title>
      <link>https://dev.to/sarav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarav"/>
    <language>en</language>
    <item>
      <title>The React Illusion: Why the Industry's Go-To Library Is Full of Hidden Friction</title>
      <dc:creator>Sarav</dc:creator>
      <pubDate>Thu, 05 Nov 2026 20:01:04 +0000</pubDate>
      <link>https://dev.to/sarav/the-react-illusion-why-the-industrys-go-to-library-is-full-of-hidden-friction-2ceb</link>
      <guid>https://dev.to/sarav/the-react-illusion-why-the-industrys-go-to-library-is-full-of-hidden-friction-2ceb</guid>
      <description>&lt;p&gt;Over a decade ago, frontend development was trapped in a cycle of manual DOM tracking and fragmented state. The introduction of React in 2013 shifted that paradigm.&lt;/p&gt;

&lt;p&gt;Before React, most applications leaned on libraries like jQuery. Developers wrote imperative, step-by-step instructions to find an HTML element, update its text, append a child, and manually keep the data layer in sync with the browser. As applications grew, this approach turned codebases into fragile "spaghetti code," where changing a single button could silently break an unrelated form elsewhere on the page.&lt;/p&gt;

&lt;p&gt;Declarative data binding already existed in AngularJS and Knockout, but React reframed the problem. It treated the UI as a function of state: you describe what the screen should look like for the current data, compose it from small components, and let data flow in one direction. React handles the DOM updates under the hood.&lt;/p&gt;

&lt;p&gt;Compared to the heavyweight MVC-style architecture of AngularJS and early enterprise frameworks, React felt lightweight, predictable, and simple to write. It put layout and logic together in components, captured the imagination of the industry, and its growth became unstoppable. Many of us fell in love with it, spending hundreds of hours on tutorials, docs, and side projects to master it.&lt;/p&gt;

&lt;p&gt;Today, React's dominance is unmatched. It has topped the Stack Overflow Developer Survey's web-framework rankings for years and appears in the majority of frontend job listings. The ecosystem is massive, the community resources are enormous, and it is the default choice for engineering teams worldwide.&lt;/p&gt;

&lt;p&gt;But when you step away from the hype and build complex, production-scale applications, you realize that the promise of "it's just JavaScript" hides a collection of structural trade-offs, architectural taxes, and frustrating quirks.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Deceptive Ergonomics of React's Rendering Rules
&lt;/h2&gt;

&lt;p&gt;React's core pitch has always been its closeness to the platform: "If you know JavaScript, you know React." The syntax is fine. The problem is that plain JavaScript semantics leak into a template context, and React adds runtime rules of its own on top.&lt;/p&gt;

&lt;h3&gt;
  
  
  The conditional rendering trap (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;In JavaScript, the logical AND operator short-circuits: if the left side is falsy, the expression returns that left-hand value. In a template, developers naturally assume a falsy condition means nothing appears on screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A natural assumption: if messages is 0, show nothing.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;div&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;messages&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You have new messages!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;The reality:&lt;/strong&gt; React renders a literal &lt;code&gt;0&lt;/code&gt; on your page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it happens:&lt;/strong&gt; &lt;code&gt;0 &amp;amp;&amp;amp; &amp;lt;p&amp;gt;…&amp;lt;/p&amp;gt;&lt;/code&gt; evaluates to the number &lt;code&gt;0&lt;/code&gt;. React skips &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, and &lt;code&gt;undefined&lt;/code&gt;, but it treats numbers (including &lt;code&gt;NaN&lt;/code&gt;) as valid content and renders them.&lt;/p&gt;

&lt;p&gt;This isn't unique to JSX. A plain template literal does the same thing:&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="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;p&amp;gt;You have new messages!&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// renders: &amp;lt;div&amp;gt;0&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real issue is that JSX uses raw JavaScript expressions as its control flow. Template languages with dedicated syntax, like &lt;code&gt;{#if}&lt;/code&gt; in Svelte or &lt;code&gt;v-if&lt;/code&gt; in Vue, evaluate a condition rather than render an expression's value, so the trap doesn't exist. In React, you have to remember to write defensive code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You have new messages!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You have new messages!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The obligatory &lt;code&gt;key&lt;/code&gt; prop
&lt;/h3&gt;

&lt;p&gt;In JSX, &lt;code&gt;.map()&lt;/code&gt; is the primary way to render lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Natural assumption: map an array straight to the UI&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;ul&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;users&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;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&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;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In development mode, React logs a warning: &lt;em&gt;"Each child in a list should have a unique 'key' prop."&lt;/em&gt; React's reconciler needs a stable identity for each item (&lt;code&gt;key={user.id}&lt;/code&gt;) to work out which elements moved, were added, or were removed between renders.&lt;/p&gt;

&lt;p&gt;To be fair, this is not unique to React. Vue requires &lt;code&gt;:key&lt;/code&gt; on &lt;code&gt;v-for&lt;/code&gt;, and Svelte has keyed each blocks (&lt;code&gt;{#each users as user (user.id)}&lt;/code&gt;). Any framework that diffs lists needs identity. What makes it a trap in React is that the warning is easy to silence with the array index. If you use &lt;code&gt;key={index}&lt;/code&gt;, then reordering, inserting, or deleting items can cause component state and uncontrolled input values to stay attached to the wrong rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Immutability Tax
&lt;/h2&gt;

&lt;p&gt;Perhaps the most tedious part of daily React development is its reliance on immutability to trigger updates. React decides whether state changed by comparing references with &lt;code&gt;Object.is&lt;/code&gt;, not by inspecting the values inside.&lt;/p&gt;

&lt;p&gt;If you mutate a nested object directly and pass the same reference back, React bails out:&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="c1"&gt;// Plain JavaScript allows direct mutation&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;San Francisco&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ React will NOT update the screen: same reference as before&lt;/span&gt;
&lt;span class="nf"&gt;setUser&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the UI to update, you must rebuild every object along the path to the changed value:&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="c1"&gt;// ⚠️ The React "spreading nightmare"&lt;/span&gt;
&lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevUser&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;prevUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;profile&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;prevUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&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;prevUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;San Francisco&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;This pattern carries real costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unreadable code.&lt;/strong&gt; Deeply nested updates become a thicket of dots, braces, and spread operators that hide the one line of intent: the city changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent data loss.&lt;/strong&gt; Forget a single &lt;code&gt;...prevUser.profile&lt;/code&gt; and you don't get an error. You quietly wipe out sibling fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocation churn.&lt;/strong&gt; The spread pattern copies only the objects along the changed path (&lt;code&gt;user → profile → address&lt;/code&gt;); untouched branches are reused by reference. That keeps the cost modest, but in state-heavy UIs that update frequently, such as live dashboards, you still generate a steady stream of short-lived objects for the garbage collector, for work that a mutable model wouldn't need at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ecosystem's standard answer is Immer (&lt;code&gt;useImmer&lt;/code&gt;, or Redux Toolkit, which uses it internally). It lets you write mutation-style code and produces the immutable copy for you. That works, but it's another dependency added to recover the ergonomics plain JavaScript already had.&lt;/p&gt;

&lt;p&gt;Compare that with fine-grained reactive frameworks. In Svelte 5, state declared with &lt;code&gt;$state&lt;/code&gt; is deeply reactive, so the natural mutation simply works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Austin&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;()&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;San Francisco&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// UI updates&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Move&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Band-Aids on Band-Aids: Memoization and the React Compiler
&lt;/h2&gt;

&lt;p&gt;When a component's state changes, React re-executes that component function from top to bottom. By default, every child it renders re-executes too, whether or not the child's props changed. (Elements passed in as &lt;code&gt;children&lt;/code&gt; from higher up are the exception, which is why the composition pattern is a common workaround.)&lt;/p&gt;

&lt;p&gt;To stop the wasted work, React historically pushed developers into a cycle of manual optimization with &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt;. Here's what keeping a single delete handler stable looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memo&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;UserRow&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;onDelete&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;onDelete&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;id&lt;/span&gt;&lt;span class="p"&gt;)&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;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;UserList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deleteUser&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Stable function identity across renders...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;deleteUser&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;deleteUser&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// forget deleteUser here and you call a stale closure&lt;/span&gt;

  &lt;span class="c1"&gt;// ...and a memoized derived value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&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;users&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&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="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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="nx"&gt;users&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;ul&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;sorted&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;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDelete&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&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;Note that all three pieces are needed. &lt;code&gt;useCallback&lt;/code&gt; alone prevents nothing: the child only skips re-rendering because it's wrapped in &lt;code&gt;React.memo&lt;/code&gt;. Miss any one of them, or get a dependency array wrong, and the optimization silently stops working. The engine doesn't figure out what needs to update; the cognitive load sits entirely with the developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The React Compiler
&lt;/h3&gt;

&lt;p&gt;The React team recognized this friction and built the React Compiler, a build-time Babel plugin that reached its stable 1.0 release in 2025 and supports React 17 and later. It analyzes your components and inserts memoization automatically, so you no longer write most &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;React.memo&lt;/code&gt; calls by hand.&lt;/p&gt;

&lt;p&gt;The compiler closes the ergonomics gap, but it is an automated band-aid on the same underlying model. At runtime, React still re-executes components, compares references, and diffs a Virtual DOM tree to work out which minimal DOM mutations to apply. Fine-grained reactive frameworks like Svelte 5 and Solid skip that step: they track which piece of state each DOM node depends on and update exactly those nodes, with no diffing.&lt;/p&gt;

&lt;p&gt;It's worth noting that Solid uses JSX too. The friction described here comes from React's runtime model, not from JSX syntax itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Architectural Divide: Server vs. Client Components
&lt;/h2&gt;

&lt;p&gt;Modern React frameworks have introduced a hard boundary between React Server Components (RSC) and Client Components.&lt;/p&gt;

&lt;p&gt;In RSC frameworks such as the Next.js App Router, components are Server Components by default and have no interactivity. The moment you need an event handler, local state, or an effect, you must mark the file with a directive at the very top, before any imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// The rest of your interactive component...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forget it, and your app errors out. Importing a hook like &lt;code&gt;useState&lt;/code&gt; into a Server Component fails at compile time; passing an &lt;code&gt;onClick&lt;/code&gt; from one fails when it renders. The directive also marks a boundary: everything that file imports becomes client code too, so where you place it shapes your bundle.&lt;/p&gt;

&lt;p&gt;(In a plain React app built with Vite or similar, none of this applies. Every component is a client component and the directive does nothing.)&lt;/p&gt;

&lt;p&gt;The divide forces engineers to constantly split layout from interactivity and reason about which side of the network each piece of code runs on. That breaks the simple, unified mental model that made React appealing in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Paradox: More Code Doesn't Equal Better Architecture
&lt;/h2&gt;

&lt;p&gt;We've entered an era where developers lean heavily on AI assistants and autonomous agents to write code. That turns the framework debate into a paradox.&lt;/p&gt;

&lt;p&gt;LLMs learn from vast amounts of public code, and after a decade of dominance, React is among the most heavily represented frameworks in that code. Models are very good at producing React: component trees, hooks, spread-based state updates, memoization wrappers, all in seconds.&lt;/p&gt;

&lt;p&gt;So developers reasonably ask: &lt;em&gt;"If my agent writes the React boilerplate for me, why should I care that it's verbose?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because the costs don't disappear; they move:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The token tax.&lt;/strong&gt; Framework authors such as Svelte's Rich Harris have long argued that the same component can be written in substantially less code in a compiled, reactive framework. Every line of boilerplate an agent generates is a line it also has to read back, costing tokens on every pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The context limit.&lt;/strong&gt; In AI-driven development, code volume is context weight. The more verbose the codebase, the fewer files fit into an agent's working context, and the less it can reason about your system's broader architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surface area for bugs.&lt;/strong&gt; Even if an AI writes 1,000 lines of React instantly, your app still executes them, your team still reviews them, and they still carry dependencies that can break in the next major upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet the loop continues: we let AI write ever more React, largely because the AI is already good at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Corporate Gravity: Why Teams Still Choose React
&lt;/h2&gt;

&lt;p&gt;Given these quirks and costs, why do teams still default to React for brand-new projects, passing over modern alternatives that compile components down to direct DOM operations?&lt;/p&gt;

&lt;p&gt;The reasons are rarely technical. They're rooted in corporate safety, ecosystem lock-in, and professional self-preservation.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Nobody ever got fired for buying IBM"
&lt;/h3&gt;

&lt;p&gt;In the era of mainframes, IT managers bought IBM because it was the safe, standard choice. If an IBM system failed, nobody blamed the manager; they blamed IBM. If they bought an alternative and it failed, their judgment was on the line.&lt;/p&gt;

&lt;p&gt;React is software engineering's modern IBM. Long backed by Meta, it's now an industry institution in the most literal sense: in October 2025, Meta announced it would move React into an independent React Foundation under the Linux Foundation, with companies such as Vercel, Microsoft, and Amazon among its founding members. If a multi-million-dollar React project misses its deadlines, leadership blames project complexity. If an architect chose a leaner alternative and hit roadblocks, the choice itself would be questioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  The network effect and job security
&lt;/h3&gt;

&lt;p&gt;React's network effect exerts a gravitational pull. Third-party vendors, payment providers, and component libraries ship their React SDKs first. For many managers, avoiding that integration risk outweighs the architectural benefits of more efficient alternatives.&lt;/p&gt;

&lt;p&gt;The job market reinforces the cycle. Developers want skills that maximize their options, and React dominates frontend job listings, so engineers push their teams toward it to keep their résumés aligned with hiring trends. It becomes self-fulfilling: companies choose React because developers know it, and developers learn React because companies hire for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing a Framework Is No Longer the Skill
&lt;/h2&gt;

&lt;p&gt;AI-assisted coding changes what engineering knowledge is worth. Knowing a specific framework's syntax is no longer a premium skill: an AI agent can recall APIs, lifecycle rules, and idioms, and translate code across ecosystems in seconds.&lt;/p&gt;

&lt;p&gt;Years of React have already taught us the durable concepts: component state, props, data flow, rendering lifecycles, and web architecture. Moving that knowledge to a leaner framework is easier than it has ever been. The skills that matter now are system design, data orchestration, and clear, simple solutions, not remembering where a &lt;code&gt;useCallback&lt;/code&gt; belongs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where React Still Wins
&lt;/h2&gt;

&lt;p&gt;None of this means React is the wrong choice everywhere. If you need to share code with a mobile app through React Native, depend on a vendor whose SDK exists only for React, or need to staff a large team quickly, React's ecosystem and hiring pool are real advantages. The point isn't that React is bad. It's that it should be a deliberate choice, not a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Call to Engineering Leaders
&lt;/h2&gt;

&lt;p&gt;React remains an industry juggernaut, and its position won't change overnight. But choosing it out of habit, fear of change, or institutional momentum holds our codebases back.&lt;/p&gt;

&lt;p&gt;Engineering leaders should evaluate their stack on its merits, not on comfort or consensus. Prototype the next greenfield project in Svelte 5, Solid, or Vue and measure the result: lines of code, bundle size, rendering performance, and how quickly the team ships. Look at the systemic health of your stack, prioritize developer velocity, and favor architectures that stay close to the web platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;React solved the problems of its time, and it solved them brilliantly. But the web has moved on. The friction we've learned to live with, from stray zeros and fragile keys to nested spreads, dependency arrays, compiler band-aids, and &lt;code&gt;"use client"&lt;/code&gt; boundaries, isn't a sign of a mature tool. It's the cost of a rendering model designed before compiled, fine-grained reactivity was practical.&lt;/p&gt;

&lt;p&gt;In an era where AI can write any framework's syntax, the deciding factors are how much code you ship, how much your team and your agents must read, and how directly your UI maps to your intent. On those measures, React is no longer the obvious winner. It's simply the familiar one.&lt;/p&gt;

&lt;p&gt;Familiarity is a reason to be careful, not a reason to stop questioning. The best engineering decisions come from measuring, not from following the crowd. It's time to step out from behind the corporate safety net of the Virtual DOM and build a simpler, faster web.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://saravi.hashnode.dev/the-react-illusion-why-the-industry-s-go-to-library-is-full-of-hidden-friction" rel="noopener noreferrer"&gt;my Hashnode blog&lt;/a&gt;. Follow me there for more posts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
