<?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>Forem: Sam Abaasi</title>
    <description>The latest articles on Forem by Sam Abaasi (@samabaasi).</description>
    <link>https://forem.com/samabaasi</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F754819%2Fc82a966a-4078-4e2b-aebe-2b7427a0abd0.jpg</url>
      <title>Forem: Sam Abaasi</title>
      <link>https://forem.com/samabaasi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/samabaasi"/>
    <language>en</language>
    <item>
      <title>The Trap of Perfectionism: Do Easy Peasy Lemon Squeezy</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sun, 03 May 2026 12:22:21 +0000</pubDate>
      <link>https://forem.com/samabaasi/the-trap-of-perfectionism-do-easy-peasy-lemon-squeezy-4ol5</link>
      <guid>https://forem.com/samabaasi/the-trap-of-perfectionism-do-easy-peasy-lemon-squeezy-4ol5</guid>
      <description>&lt;p&gt;I've spent years writing articles — and almost as many years not publishing them.&lt;/p&gt;

&lt;p&gt;Not because I had nothing to say. I had &lt;em&gt;everything&lt;/em&gt; to say. Every time I solved a hard problem or finally understood something deeply — I wrote it down. That feeling of a light turning on after days of confusion? I wanted to give that to someone else.&lt;/p&gt;

&lt;p&gt;But perfectionism had other plans.&lt;/p&gt;




&lt;h2&gt;
  
  
  Drafts Everywhere
&lt;/h2&gt;

&lt;p&gt;Medium. DEV Community. Google Docs. Chunks of paper stuffed inside books, sitting at the bottom of my laptop bag, falling out when I least expect it.&lt;/p&gt;

&lt;p&gt;But I never wanted to publish something incomplete. No article without proper diagrams. No explanation that left gaps. It had to be whole — or it wasn't going out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzc87p65h3uard9evj13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzc87p65h3uard9evj13.png" alt="Medium.com/@samabaasi" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So every draft just sat there — waiting for the perfect diagram, the perfect depth of knowledge, the feeling that I truly understood &lt;em&gt;every&lt;/em&gt; part of the topic before I had the right to explain any of it.&lt;/p&gt;

&lt;p&gt;Three years of "not yet."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qc3mstujk9s24z0brv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qc3mstujk9s24z0brv1.png" alt="Dev.to/samabaasi" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Was Perfectionist About
&lt;/h2&gt;

&lt;p&gt;Not grammar. Not formatting. Something deeper.&lt;/p&gt;

&lt;p&gt;I wanted to make it &lt;strong&gt;easy to understand&lt;/strong&gt; — the kind of explanation where someone finishes and thinks &lt;em&gt;why didn't anyone explain it like this before?&lt;/em&gt; I wanted &lt;strong&gt;great visualizations&lt;/strong&gt; that make things &lt;em&gt;clear&lt;/em&gt;. I wanted to &lt;strong&gt;cover everything&lt;/strong&gt; so no one gets lost halfway.&lt;/p&gt;

&lt;p&gt;Underneath all of that was a quiet fear: the fear of not being good enough. Of not actually helping someone the way they needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing Jack Harrington Says
&lt;/h2&gt;

&lt;p&gt;Jack Harrington says it in almost every video:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Easy peasy lemon squeezy.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At some point I actually &lt;em&gt;heard&lt;/em&gt; what he was saying — if you understand something well enough, you can make it feel light for someone else. The complexity is real. The explanation doesn't have to be heavy.&lt;/p&gt;

&lt;p&gt;And he uses tools. He doesn't spend three days hand-crafting a perfect diagram when ChatGPT can make a clear one in five minutes.&lt;/p&gt;

&lt;p&gt;So I started doing the same. And suddenly the wall was gone.&lt;/p&gt;

&lt;p&gt;The goal was never to be perfect. It was always that moment — when something clear for someone. A perfect article in a draft helps nobody. An honest one out in the world might change how someone thinks forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Cut the Code. I Kept the Story.
&lt;/h2&gt;

&lt;p&gt;I stopped trying to cover everything. Instead I focused on the &lt;strong&gt;problem-solution mindset&lt;/strong&gt; — don't just show the answer, show the &lt;em&gt;journey&lt;/em&gt;. The steps, the wrong turns, how the solution was actually found.&lt;/p&gt;

&lt;p&gt;That's how I learn best. Not from clean final answers — from watching someone walk the path. You can see it in my React series: articles that don't just explain how React works, but trace &lt;em&gt;why&lt;/em&gt; each piece exists. The journey, not just the destination.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Finally Got Published
&lt;/h2&gt;

&lt;p&gt;Two series. Many articles. Years of drafts — finally out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚛️ How React Works Under the Hood&lt;/strong&gt; — 9 parts. From why Fiber exists to Server Components and hydration. The series I most wanted to find when I was learning and never could.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd12nuifop5ss3yfsuap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd12nuifop5ss3yfsuap.png" alt="How React Works Under the Hood" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Extending bpmn-io Form-JS Beyond Its Limits&lt;/strong&gt; — 23 parts. The architecture the official docs never wrote. Everything I had to discover through trial and error — so you don't have to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0dggdn9ex06zdlai3m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0dggdn9ex06zdlai3m0.png" alt="Extending bpmn-io Form-JS Beyond Its Limits" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;Wanting clarity, great visuals, and depth — none of that is wrong.&lt;/p&gt;

&lt;p&gt;But perfectionism becomes a trap the moment it stops making the work &lt;em&gt;better&lt;/em&gt; and starts stopping the work from &lt;em&gt;existing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So — what's yours? What have you been sitting on because it's just not ready yet?&lt;/p&gt;

&lt;p&gt;Easy peasy lemon squeezy — not because it's easy. Because you did the hard work, so the reader doesn't have to do it alone.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sam Abaasi — Frontend Engineer&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://medium.com/@samabaasi" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; · &lt;a href="https://dev.to/samabaasi"&gt;DEV Community&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/samabaasi/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>How React Works (Part 9)? The Complete Picture: From a Keystroke to a Pixel</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 14:46:35 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-9-the-complete-picture-from-a-keystroke-to-a-pixel-3k3n</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-9-the-complete-picture-from-a-keystroke-to-a-pixel-3k3n</guid>
      <description>&lt;h2&gt;
  
  
  The Complete Picture: From a Keystroke to a Pixel
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;This is the final article.&lt;/strong&gt; If you haven't read Parts 1–8, this article won't land the way it should. Go back and start there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok"&gt;The Idea That Makes Suspense Possible&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4"&gt;The React Lifecycle From the Inside&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-6-how-state-actually-works-usestate-from-the-inside-1on"&gt;How State Actually Works&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 7:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-7-the-trap-of-vibe-coding-usecallback-25cg"&gt;The Trap of Vibe Coding useCallback&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 8:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-8-server-components-hydration-the-real-story-521h"&gt;Server Components &amp;amp; Hydration: The Real Story&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Eight Articles. One Question.
&lt;/h2&gt;

&lt;p&gt;In Part 1, Dan Abramov stood in front of a room in Iceland and asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"With vast differences in computing power and network speed, how do we deliver the best user experience for everyone?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've spent eight articles building the answer. But we've never seen all the pieces working together at once. We've seen Fiber explained. The Scheduler explained. The Reconciler. Algebraic effects. Lifecycle. State. Performance. Server Components. Each one in isolation.&lt;/p&gt;

&lt;p&gt;This article puts them together. Not as a summary — as a story. One user. One interaction. Every layer of React underneath it.&lt;/p&gt;

&lt;p&gt;By the end, you'll see that everything we built across eight articles was always one system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The User
&lt;/h2&gt;

&lt;p&gt;She opens a product search page on a mid-range Android phone — the kind of device hundreds of millions of people actually use. Her connection is decent but not fast. She's looking for headphones.&lt;/p&gt;

&lt;p&gt;She types. She clicks. She adds something to her cart.&lt;/p&gt;

&lt;p&gt;From her perspective: the page loads, she types, results appear, she clicks a button, the cart updates. Four seconds of her life.&lt;/p&gt;

&lt;p&gt;Underneath those four seconds, every system in this series ran. Let's trace it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Page Loads
&lt;/h2&gt;

&lt;p&gt;The request hits the server. This is Part 8's world.&lt;/p&gt;

&lt;p&gt;Server Components run — async, directly querying the database. The product catalog, the navigation, the page layout. None of this code ships to her phone. It renders on the server and becomes the RSC payload — a JSON description of the UI that streams to the client in chunks. Her phone gets real HTML almost immediately, not a white screen waiting for JavaScript.&lt;/p&gt;

&lt;p&gt;While the HTML is visible, React starts hydration in the background. It walks the server-rendered DOM and matches each node to its Fiber counterpart — attaching event listeners, storing references, making the page interactive. Because React 18 uses the same Lane priority system from Part 3, if she taps something before hydration finishes, React jumps to that boundary first. The rest of the tree waits. Her tap doesn't go ignored.&lt;/p&gt;

&lt;p&gt;The page is ready. She types.&lt;/p&gt;




&lt;h2&gt;
  
  
  She Types "h"
&lt;/h2&gt;

&lt;p&gt;This is where Parts 2, 3, and 6 all meet.&lt;/p&gt;

&lt;p&gt;A change event fires. React calls &lt;code&gt;setQuery('h')&lt;/code&gt;. Calling &lt;code&gt;setQuery&lt;/code&gt; doesn't immediately update anything — it creates an update object, drops it into a global buffer, and marks a trail called &lt;code&gt;childLanes&lt;/code&gt; on every ancestor of the search input fiber, all the way to the root. This is how React knows where work is waiting without walking the entire tree.&lt;/p&gt;

&lt;p&gt;The Scheduler sees the update. Because this is a user interaction, it's &lt;code&gt;SyncLane&lt;/code&gt; — the highest priority. It runs synchronously. The call stack equivalent of "drop everything and handle this."&lt;/p&gt;

&lt;p&gt;React walks the Fiber tree following the trail of &lt;code&gt;childLanes&lt;/code&gt;. Every branch with &lt;code&gt;childLanes === 0&lt;/code&gt; is skipped instantly — the footer, the recommendations panel, the filters sidebar. React touches only what needs to change. The input updates. The character appears on screen.&lt;/p&gt;

&lt;p&gt;That took a few milliseconds. She doesn't notice. She keeps typing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results Need to Update
&lt;/h2&gt;

&lt;p&gt;Here's the problem that Part 1 opened with — still alive, still relevant.&lt;/p&gt;

&lt;p&gt;Filtering 10,000 products on every keystroke is expensive. If React treated the results update with the same urgency as the input update, every character she types would block until the filtering finished. The input would lag. The experience would feel broken.&lt;/p&gt;

&lt;p&gt;This is exactly Dan's typing + chart demo from 2018. The chart is the results list. It has to update. But it shouldn't block the input.&lt;/p&gt;

&lt;p&gt;The fix is &lt;code&gt;startTransition&lt;/code&gt; — wrapping the expensive update so React knows it can wait. The input still updates immediately at &lt;code&gt;SyncLane&lt;/code&gt;. The results update in the background at &lt;code&gt;TransitionLane&lt;/code&gt;, interruptibly:&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="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="c1"&gt;// urgent — runs now&lt;/span&gt;
&lt;span class="nf"&gt;startTransition&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;setResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;  &lt;span class="c1"&gt;// deferrable — runs when free&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire mechanism. &lt;code&gt;startTransition&lt;/code&gt; tells React: this update is real but not urgent. Internally, React assigns it a &lt;code&gt;TransitionLane&lt;/code&gt; instead of &lt;code&gt;SyncLane&lt;/code&gt;. The Scheduler now has two tasks — the keystroke at the top, the results render below it.&lt;/p&gt;

&lt;p&gt;The results render begins using the concurrent work loop — the one that checks &lt;code&gt;shouldYield()&lt;/code&gt; after every fiber. She types another character. A new &lt;code&gt;SyncLane&lt;/code&gt; keystroke arrives. React &lt;strong&gt;abandons the results render mid-tree&lt;/strong&gt;, handles the keystroke — she sees it immediately — then starts the results render again with the new query.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;isPending&lt;/code&gt; tells you the transition is in progress, so you can show a spinner or dim the old results while new ones load. The old results stay visible until the render completes. She never sees a blank list.&lt;/p&gt;

&lt;p&gt;This is the Git metaphor from Part 1 made real: the keystroke is always the hotfix on main. The results render is always the feature branch. React rebases automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlelylo297rykvoiqmtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlelylo297rykvoiqmtj.png" alt="SyncLane keystroke interrupts TransitionLane results render — old results stay visible, isPending true" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you can't wrap the setter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the state lives in a child component or a library you don't control. &lt;code&gt;useDeferredValue&lt;/code&gt; solves this — it gives you a deferred copy of a value that React renders at lower priority, interruptibly, without you needing to touch the setter:&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;deferredQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDeferredValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ResultList receives deferredQuery — renders at TransitionLane, same mechanism&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical rule: use &lt;code&gt;useTransition&lt;/code&gt; when you own the setter, &lt;code&gt;useDeferredValue&lt;/code&gt; when you own the value but not where it comes from.&lt;/p&gt;




&lt;h2&gt;
  
  
  React Finds What Changed
&lt;/h2&gt;

&lt;p&gt;She paused typing. The results render runs to completion. This is Part 3.&lt;/p&gt;

&lt;p&gt;React walks the &lt;code&gt;ResultList&lt;/code&gt; fiber, runs the component function, gets new JSX. The Reconciler compares old to new. Each product has a &lt;code&gt;key&lt;/code&gt; prop — its ID. React builds a map of old fibers keyed by product ID. For each new product, it looks up the existing fiber. Found: reuse it, update only what changed. Not found: create it. Products no longer in results: mark for deletion.&lt;/p&gt;

&lt;p&gt;React doesn't touch the DOM during any of this. It marks fibers with flags — insert this, update that, delete this — and bubbles those flags up the tree. By the time it reaches the root, every decision about what needs to change is recorded. Nothing has changed in the DOM yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DOM Updates
&lt;/h2&gt;

&lt;p&gt;The Commit phase runs — synchronous, uninterruptible. The two Fiber trees swap. The workInProgress tree becomes current. What was being built in the background is now the source of truth.&lt;/p&gt;

&lt;p&gt;DOM operations execute in order: deletions first, then insertions, then updates. Products that disappeared are removed. New products are inserted. Changed cards get their props updated directly on the existing DOM node — no recreation.&lt;/p&gt;

&lt;p&gt;The browser paints. She sees the filtered results.&lt;/p&gt;




&lt;h2&gt;
  
  
  She Clicks "Add to Cart"
&lt;/h2&gt;

&lt;p&gt;The click fires. &lt;code&gt;setCartCount(c =&amp;gt; c + 1)&lt;/code&gt; is called. Same machinery as the keystroke — &lt;code&gt;SyncLane&lt;/code&gt;, update queued, trail marked up to the root, Scheduler fires synchronously.&lt;/p&gt;

&lt;p&gt;React walks the trail. Everything outside the &lt;code&gt;Header&lt;/code&gt; subtree has &lt;code&gt;childLanes === 0&lt;/code&gt; — the results list, the filters, the footer — all skipped in one check each. React touches only the &lt;code&gt;Header&lt;/code&gt; and its &lt;code&gt;CartIcon&lt;/code&gt; child.&lt;/p&gt;

&lt;p&gt;The render runs. The commit runs. Now Part 5 takes over.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useLayoutEffect&lt;/code&gt; fires synchronously — the DOM is updated, the browser hasn't painted yet. If &lt;code&gt;CartIcon&lt;/code&gt; needs to measure its width for an animation, this is the only safe moment. Then the browser paints. She sees the cart count increment.&lt;/p&gt;

&lt;p&gt;In the next macro task — after paint, via &lt;code&gt;MessageChannel&lt;/code&gt; — &lt;code&gt;useEffect&lt;/code&gt; runs. The analytics event fires. localStorage syncs. None of this blocked the paint. None of it delayed what she saw.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft801gce7o8wzrgnmyypb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft801gce7o8wzrgnmyypb.png" alt="cart click lifecycle — SyncLane → render → commit → useLayoutEffect → paint → useEffect" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Didn't Run
&lt;/h2&gt;

&lt;p&gt;Of the entire component tree — dozens of components, hundreds of fibers — exactly three ran their render functions for the cart click:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The root — has &lt;code&gt;childLanes &amp;gt; 0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Header&lt;/code&gt; — has &lt;code&gt;childLanes &amp;gt; 0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CartIcon&lt;/code&gt; — the state changed here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else: &lt;code&gt;ResultList&lt;/code&gt;, &lt;code&gt;SearchInput&lt;/code&gt;, &lt;code&gt;Filters&lt;/code&gt;, &lt;code&gt;Footer&lt;/code&gt;, &lt;code&gt;Recommendations&lt;/code&gt;, every &lt;code&gt;ProductCard&lt;/code&gt; — skipped. Not because of &lt;code&gt;React.memo&lt;/code&gt;. Not because of &lt;code&gt;useCallback&lt;/code&gt;. Because &lt;code&gt;childLanes&lt;/code&gt; was zero on every other subtree, and React's built-in bailout skipped each one in a single check.&lt;/p&gt;

&lt;p&gt;This is the answer to Part 7. The tools exist for the rare cases where the built-in bailout isn't enough. For everything else — the default behavior handles it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Map
&lt;/h2&gt;

&lt;p&gt;Eight parts. One answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fiber&lt;/strong&gt; is the foundation. React replaced the opaque JavaScript call stack with its own — plain objects it controls completely. This made pausing, resuming, and prioritizing possible. Without Fiber, nothing else in this series exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Scheduler&lt;/strong&gt; sits on top of Fiber and decides what runs and when. Keystrokes get &lt;code&gt;SyncLane&lt;/code&gt; — they always run first, synchronously. Results renders get &lt;code&gt;TransitionLane&lt;/code&gt; — they run in the background, interruptibly. &lt;code&gt;shouldYield()&lt;/code&gt; checks the clock after every fiber to keep the browser breathing between frames.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Reconciler&lt;/strong&gt; uses the Fiber tree to find the minimum set of DOM changes. It follows the &lt;code&gt;childLanes&lt;/code&gt; trail to find what changed, skips everything with clean subtrees, diffs lists by key, and marks flags on fibers — never touching the DOM until the entire decision is made. Then the Commit phase applies everything atomically so the user never sees a half-updated UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Algebraic Effects&lt;/strong&gt; is the mental model that connects Suspense, ErrorBoundary, and hooks. Components signal what they need by throwing. React — acting as the effect handler — catches it, decides what to do, and resumes when the data arrives. &lt;code&gt;useState&lt;/code&gt; feels local because of this model, even though state lives on a Fiber node React controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifecycle&lt;/strong&gt; is when effects run relative to the Commit phase. &lt;code&gt;useLayoutEffect&lt;/code&gt; fires synchronously before the browser paints — for DOM measurements that must happen before the user sees anything. &lt;code&gt;useEffect&lt;/code&gt; fires after paint in the next macro task — for everything else. The separation isn't arbitrary. It maps directly to the Commit phase structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State&lt;/strong&gt; is a hook object on a Fiber's linked list. Calling &lt;code&gt;setState&lt;/code&gt; doesn't update immediately — it drops a note in a queue. React reads that queue during the next render, on its own schedule, in its own order. This is why you see the old value after calling &lt;code&gt;setState&lt;/code&gt;. The snapshot hasn't changed yet. The note is pending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; is mostly free. The &lt;code&gt;childLanes&lt;/code&gt; bailout skips entire subtrees in one check when nothing changed. Structural patterns like children-as-props prevent unnecessary re-renders without any memoization overhead. &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;useMemo&lt;/code&gt; exist for the small set of cases where the built-in bailout genuinely isn't enough — and they should only be added after measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server Components&lt;/strong&gt; moved the question from "how do we render faster on the client" to "how do we send less to the client in the first place." Server code that never ships. RSC payloads that stream progressively. Selective hydration that prioritizes what the user is interacting with. Every Server Action a public endpoint — treat it like one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Still Being Built
&lt;/h2&gt;

&lt;p&gt;The series ends here but React doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The React Compiler&lt;/strong&gt; is stable and shipping. It automatically applies the memoization from Part 7 at build time — &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt; — applied intelligently, without you writing any of it. Understanding this series is exactly what makes your code Compiler-friendly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;use()&lt;/code&gt;&lt;/strong&gt; is the ergonomic layer on top of the algebraic effects model from Part 4. Where Suspense required a data library to implement the throw-Promise pattern, &lt;code&gt;use()&lt;/code&gt; exposes it directly from React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server Actions security&lt;/strong&gt; is still maturing. React2Shell revealed a persistent gap between the mental model (local function) and the reality (public HTTP endpoint). The ecosystem is figuring out better defaults. Until then: validate everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One-Sentence Answer
&lt;/h2&gt;

&lt;p&gt;React delivers the best user experience for everyone by giving developers a declarative component model while handling — invisibly — the complexity of prioritizing work, interrupting expensive renders, deferring non-urgent updates, minimizing DOM changes, and progressively delivering content from server to client, through the Fiber and Scheduler machinery built for exactly this purpose.&lt;/p&gt;

&lt;p&gt;Eight articles to earn one sentence.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Have Now
&lt;/h2&gt;

&lt;p&gt;Before this series, React was a tool you used. After it, React is a system you understand.&lt;/p&gt;

&lt;p&gt;The next time an input lags, you know it's a &lt;code&gt;SyncLane&lt;/code&gt; update blocked by a lower-priority render — and you know &lt;code&gt;startTransition&lt;/code&gt; is the right fix, not &lt;code&gt;useCallback&lt;/code&gt;. The next time a component re-renders unexpectedly, you know to check the reference equality of its props and the &lt;code&gt;childLanes&lt;/code&gt; trail — not to wrap everything in &lt;code&gt;React.memo&lt;/code&gt;. The next time a page loads slowly, you know the difference between a bundle size problem (Server Components), a data waterfall problem (streaming and Suspense), and a render performance problem (Fiber and Reconciler).&lt;/p&gt;

&lt;p&gt;The tools haven't changed. But the questions you ask before reaching for them have.&lt;/p&gt;

&lt;p&gt;That's what understanding internals actually gives you: not the ability to contribute to React's source code, but the ability to debug faster, make better decisions earlier, and stop adding complexity to fix problems you only half-understood.&lt;/p&gt;

&lt;p&gt;Write something. Ship it. Now you'll know why it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Thank You
&lt;/h2&gt;

&lt;p&gt;This series exists because of the people who built these systems and took the time to explain them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dan Abramov&lt;/strong&gt; — for Beyond React 16, the Git metaphor that makes Time Slicing click, RSC from Scratch, Algebraic Effects for the Rest of Us, and overreacted.io. More teaching in one career than most people manage in ten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Andrew Clark&lt;/strong&gt; — for the react-fiber-architecture document that set the design goals every article in this series built toward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sebastian Markbåge&lt;/strong&gt; — for the algebraic effects mental model that connects Suspense, ErrorBoundary, Hooks, and Context into one coherent story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matheus Albuquerque&lt;/strong&gt; — for the clearest explanation of FiberNode internals at React Summit 2022.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sam Galson&lt;/strong&gt; — for connecting React to the wider computer science of fibers, coroutines, and continuations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;JSer (jser.dev)&lt;/a&gt;&lt;/strong&gt; — for the deepest source-level analysis of React internals anywhere on the internet. Every code-level claim in this series was verified against JSer's work. An extraordinary resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sophie Alpert, Joe Savona, Luna Wei&lt;/strong&gt; — for the React team's continued work on documentation, RFCs, and honest discussion of tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for JavaScript visualizations that shaped this series' style from the beginning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/lachlan2k" rel="noopener noreferrer"&gt;Lachlan Davidson&lt;/a&gt;&lt;/strong&gt; — for responsibly disclosing CVE-2025-55182 and working with the React team to fix it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;That's the series. Thank you for reading. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>hooks</category>
    </item>
    <item>
      <title>How React Works (Part 8)? Server Components &amp; Hydration: The Real Story</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 14:32:09 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-8-server-components-hydration-the-real-story-521h</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-8-server-components-hydration-the-real-story-521h</guid>
      <description>&lt;h2&gt;
  
  
  Server Components &amp;amp; Hydration: The Real Story
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok"&gt;The Idea That Makes Suspense Possible&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4"&gt;The React Lifecycle From the Inside&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-6-how-state-actually-works-usestate-from-the-inside-1on"&gt;How State Actually Works&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 7:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-7-the-trap-of-vibe-coding-usecallback-25cg"&gt;The Trap of Vibe Coding useCallback&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1–7 first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where We Left Off
&lt;/h2&gt;

&lt;p&gt;Through Parts 1–7, every problem we solved lived on the client. The call stack couldn't pause — so React built Fiber. Re-rendering was too slow — so React built the Reconciler. State management was confusing — because it's a queue on a Fiber node, not a variable. Performance tools were overused — because developers reached for them before understanding the problem.&lt;/p&gt;

&lt;p&gt;Every solution stayed inside the browser. The user's device was the bottleneck. The network was just the delivery mechanism.&lt;/p&gt;

&lt;p&gt;Part 8 changes that. Because there's a whole category of problem that no client-side optimization can fix — and it took the React team until 2023 to fully solve it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;You've shipped a React app. You've done everything right. You memoized where it mattered. You fixed re-renders. You code-split your routes. React DevTools shows a clean profile. Your API is fast.&lt;/p&gt;

&lt;p&gt;Then you open Chrome on a mid-range Android phone with a throttled 3G connection — the kind of device hundreds of millions of people actually use — and you watch the screen stay white for four seconds before anything appears.&lt;/p&gt;

&lt;p&gt;You look at the Network tab. Before your user sees a single pixel of your app, their phone downloaded, parsed, and executed 380kb of JavaScript. You open the bundle analyzer. Half of that JavaScript is components that render completely static content — a product description that never changes, a nav with hardcoded links, a footer, a terms page. None of them use state. None of them handle events. They just render text and markup.&lt;/p&gt;

&lt;p&gt;But they ship to every client. On every load. On every device. Including that phone on 3G that Part 1 opened with.&lt;/p&gt;

&lt;p&gt;This is the problem that Part 1's question — &lt;em&gt;"with vast differences in computing power and network speed, how do we deliver the best user experience for everyone?"&lt;/em&gt; — still hadn't fully answered by the end of Part 7. We optimized what happens on the client. But we never questioned why so much work was happening on the client in the first place.&lt;/p&gt;

&lt;p&gt;The naive fix was server-side rendering. But SSR had a fundamental flaw that took years to properly name — and understanding that flaw is what makes Server Components make sense.&lt;/p&gt;







&lt;h2&gt;
  
  
  The Problem: The Two-Trip Lie
&lt;/h2&gt;

&lt;p&gt;To understand why Server Components exist, you need to go back to how server rendering worked before them.&lt;/p&gt;

&lt;p&gt;Dan Abramov explained the origin story in his RSC from Scratch deep dive by taking you back to 2003. PHP on a server: the server gets a request, reads a file or a database, returns HTML. Simple. The user sees content immediately. No JavaScript bundle to download. No client-side rendering. Just a server doing work and returning a result.&lt;/p&gt;

&lt;p&gt;React brought rich interactivity but also a cost: everything runs in the browser. Large bundles. Slow initial loads on slow connections. So the industry added server rendering back — render HTML on the server first, send it to the client, then download JavaScript and "hydrate" the page to make it interactive.&lt;/p&gt;

&lt;p&gt;This solved the blank screen problem. But it created what the React team calls a two-trip problem.&lt;/p&gt;

&lt;p&gt;The server renders HTML — fast, visible immediately. Then the browser downloads all the JavaScript anyway. React runs again on the client. Re-does the work. Attaches event listeners. Any data fetched on the server to render the HTML? Fetched again on the client. Bundle size? Unchanged — every component that ran on the server also ships to the client. Server work didn't reduce client work. It looked like a performance win but the full cost came later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnp6mxefyl5ify6aewck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnp6mxefyl5ify6aewck.png" alt="old SSR — server renders HTML → client downloads all JS → React runs again → full cost paid twice" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the problem Server Components actually solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Hydration Actually Is — and What's Wrong With It
&lt;/h2&gt;

&lt;p&gt;Server-side rendering gave React apps faster first paint. But it introduced a problem that almost nobody explained clearly: &lt;strong&gt;the page looks ready before it is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server renders HTML and sends it. The user sees a complete-looking page — the nav, the content, the buttons. They try to click something. Nothing happens. The button doesn't respond. The form doesn't submit. The dropdown doesn't open. The page looks interactive but it isn't — because React hasn't attached any event listeners yet.&lt;/p&gt;

&lt;p&gt;This is the hydration gap. And it exists because of how hydration works.&lt;/p&gt;

&lt;p&gt;From jser.dev's hydration internals: hydration is not re-rendering. React does not recreate the DOM. Instead, React walks the existing server-rendered DOM tree in parallel with the Fiber tree it's building, matching each fiber to its corresponding DOM node — storing a reference in &lt;code&gt;fiber.stateNode&lt;/code&gt;. Event listeners are attached. The DOM is reused. This is why hydration is faster than a fresh render.&lt;/p&gt;

&lt;p&gt;But the entire tree must hydrate before any part becomes interactive. One slow component at the top of the tree blocks the click handlers on a button at the bottom. React walks the tree sequentially, and until that walk is complete, nothing responds.&lt;/p&gt;

&lt;p&gt;React 18 fixed this with &lt;strong&gt;selective hydration&lt;/strong&gt;. Using the same Lanes priority system from Part 3, React can now interrupt hydration to handle a user interaction first. If a user clicks a button before hydration reaches it, React jumps to that boundary using &lt;code&gt;SyncLane&lt;/code&gt; — hydrates just enough to handle the interaction — then resumes the rest of the tree. The architecture from Part 3 extends directly here: interruptible work, priority-driven, same system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oj19gc687jciwpi7824.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oj19gc687jciwpi7824.png" alt="old hydration — sequential, blocked / selective hydration — user click interrupts, React jumps to that boundary first" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Streaming takes this further still. Instead of waiting for all data before sending any HTML, the server sends the shell immediately and streams each Suspense boundary as its data resolves. The client starts hydrating what arrived while the rest is still coming. Multiple chunks, progressive — no blank screen waiting for the slowest database query.&lt;/p&gt;

&lt;p&gt;But even with selective hydration and streaming, there's still a cost that SSR never eliminated. The JavaScript still ships. Every component still runs on the client. That's the problem Server Components solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Server Components Actually Are
&lt;/h2&gt;

&lt;p&gt;From the React Labs March 2023 blog post, written by the React team:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We are introducing a new kind of component — Server Components — that run ahead of time and are excluded from your JavaScript bundle. Server Components can run during the build, letting you read from the filesystem or fetch static content. They can also run on the server, letting you access your data layer without having to build an API."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Server Components run only on the server. Their code never ships to the client. Client Components — marked explicitly with &lt;code&gt;'use client'&lt;/code&gt; — run on the client as before. The boundary between them is a decision you make explicitly at every component.&lt;/p&gt;

&lt;p&gt;The critical constraint the boundary enforces: &lt;strong&gt;functions cannot cross from Server to Client.&lt;/strong&gt; Functions aren't serializable. If you try to pass a function as a prop from a Server Component to a Client Component, React throws an error. Only plain serializable values can cross — strings, numbers, objects, arrays, React elements. This shapes every RSC architecture decision. It's why you can't pass event handlers from the server side.&lt;/p&gt;

&lt;p&gt;What the server sends is not HTML and not JavaScript. It's the &lt;strong&gt;RSC payload&lt;/strong&gt; — a JSON-like description of the rendered UI tree. Here's what a simple RSC payload actually looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;J&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;:&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="s2"&gt;"div"&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;span class="nl"&gt;"className"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;J&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&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="s2"&gt;"h1"&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;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;J&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&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="s2"&gt;"p"&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;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Content here"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line is a serialized React element — type, key, ref, props. React on the client reads this and builds or updates its Fiber tree from it. As Dan's RSC from Scratch describes: &lt;em&gt;"a production-ready RSC setup sends JSX chunks as they're being produced instead of a single large blob at the end. When React loads, hydration can start immediately — React starts traversing the tree using the JSX chunks that are already available."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For Client Components, only their props get serialized into the payload — not their code, which stays in the bundle. The payload contains a reference to the Client Component (a module ID the bundler knows about) plus the props to render it with.&lt;/p&gt;

&lt;p&gt;This is why RSC works with client-side navigation: navigating to a new page in a Next.js App Router app doesn't reload the whole page. It fetches a new RSC payload and React merges it into the existing Fiber tree — same mechanism, different data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf42vax0sw8xvfuvqnlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf42vax0sw8xvfuvqnlh.png" alt="RSC payload flow — Server Component renders → JSON-like payload streamed → client Fiber tree built/updated" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The React docs explain this model clearly — the boundary rules, how composition works, how to think about what goes where. It's genuinely good documentation.&lt;/p&gt;

&lt;p&gt;What the docs didn't prepare developers for: the mental model they encourage — writing &lt;code&gt;async function submitOrder()&lt;/code&gt; marked &lt;code&gt;'use server'&lt;/code&gt; and calling it like any local function — is exactly the mental model that skips the input validation you'd never skip on an API route. In December 2025, that gap cost real companies their servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Security Reality: React2Shell
&lt;/h2&gt;

&lt;p&gt;On November 29th, 2025, security researcher Lachlan Davidson reported a vulnerability to Meta's Bug Bounty program. Four days later, the React team disclosed it publicly.&lt;/p&gt;

&lt;p&gt;From the official React blog post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"There is an unauthenticated remote code execution vulnerability in React Server Components."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;CVE-2025-55182 — CVSS 10.0.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The root cause: React Server Functions (Server Actions) allow a client to call a function on the server. React translates the client's request into an HTTP request, which the server deserializes and executes. The deserialization process did not properly validate inputs. An attacker could craft a malicious HTTP request that, when deserialized by React, achieved remote code execution on the server — &lt;strong&gt;with no authentication required, against any app using React Server Components.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The React blog was direct: &lt;em&gt;"Even if your app does not implement any React Server Function endpoints it may still be vulnerable if your app supports React Server Components."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Exploits appeared within 24 hours of disclosure. Unit 42 at Palo Alto Networks documented post-exploitation activity including reverse shells, malware installation, and activity linked to state-sponsored threat actors.&lt;/p&gt;

&lt;p&gt;Two follow-on CVEs were disclosed days later: source code exposure (CVE-2025-55183 — a crafted request could cause the server to return its own source code) and denial of service (CVE-2025-55184 — a crafted request caused an infinite loop that hung the server process permanently). The initial fix for the DoS was incomplete, requiring a second patch under CVE-2025-67779. A fourth vulnerability was disclosed in January 2026 (CVE-2026-23864).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for you as a developer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every Server Action (&lt;code&gt;'use server'&lt;/code&gt; function) is a public HTTP endpoint. Anyone on the internet can send a POST request to it — not just your frontend. There is no framework magic that makes inputs safe by default. You must validate and sanitize every argument exactly as you would validate input to an Express route handler or an API endpoint.&lt;/p&gt;

&lt;p&gt;The mistake RSC encourages is writing server-side logic that &lt;em&gt;feels&lt;/em&gt; like an internal function — you're just calling &lt;code&gt;submitForm()&lt;/code&gt; from a button click, it looks local. But it's an HTTP endpoint. Untrusted input. Treat it that way.&lt;/p&gt;

&lt;p&gt;Additionally: whatever you pass as props from a Server Component to a Client Component travels in the RSC payload — visible in the browser's network tab. Pass only what the client needs to render. Passing an entire database row when only a name is needed exposes the rest of that data to anyone watching the network response.&lt;/p&gt;







&lt;h2&gt;
  
  
  A Concrete Trace: What Actually Happens on Navigation
&lt;/h2&gt;

&lt;p&gt;Let's make this real. When a user clicks a link in a Next.js App Router app:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User clicks a link.&lt;/strong&gt; React intercepts the navigation. Instead of a full page reload, it fetches the RSC payload for the new route from the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server processes the request.&lt;/strong&gt; Server Components for the new route run — async, querying databases, reading files. They render to the RSC payload format. Client Component boundaries are replaced with module references plus serialized props.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payload streams to the client.&lt;/strong&gt; The response arrives in chunks. React starts processing chunks as they arrive — it doesn't wait for the complete response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React merges the payload into the Fiber tree.&lt;/strong&gt; For unchanged parts (shared layout, nav), React skips re-rendering entirely. For new content, React creates or updates fibers from the payload. Client Components receive their serialized props and render on the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The page updates.&lt;/strong&gt; No full reload. No loss of client-side state. The URL changes. The content changes. Client Components that were already hydrated stay hydrated.&lt;/p&gt;

&lt;p&gt;This is the genuine architectural win: &lt;strong&gt;the server does the data work, the client does the interaction work, and the payload is the bridge between them.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vibe Coding RSC Trap
&lt;/h2&gt;

&lt;p&gt;In Part 7 we saw what happens when developers add &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;React.memo&lt;/code&gt; everywhere without measuring — code that's harder to read, harder to debug, and often slower than before.&lt;/p&gt;

&lt;p&gt;The same pattern exists with Server Components. You read the Next.js App Router docs. You migrate your pages directory. You follow the examples. And you end up writing code like this:&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;// Every component that uses state or events gets 'use client'&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Header&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchInput&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductCard&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Footer&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="c1"&gt;// doesn't even use state — added just in case&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component is a Client Component. Every component ships JavaScript to the client. The RSC architecture is in place — the routing, the layout, the page files — but the actual benefit (JavaScript that never ships) is zero. You've added the complexity of the Server/Client boundary, the mental overhead of &lt;code&gt;'use client'&lt;/code&gt; everywhere, and the security surface of Server Actions — for a bundle size identical to before.&lt;/p&gt;

&lt;p&gt;This happens because the docs show you the mechanics but not the principle. The principle is simple: &lt;strong&gt;a component should only be a Client Component if it genuinely needs the client&lt;/strong&gt; — state, effects, browser APIs, event handlers. Everything else can be a Server Component. A product description that just renders text? Server Component. A nav with static links? Server Component. A footer? Server Component. The question to ask for every component is: does this need to run in the browser?&lt;/p&gt;

&lt;p&gt;The vibe coding version asks: does the tutorial mark this as &lt;code&gt;'use client'&lt;/code&gt;? If yes, add it. If the app breaks, add more. This produces an RSC app that costs more than the original — in complexity, in security surface, in developer confusion — and delivers nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qr118305i31s7rlptyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qr118305i31s7rlptyl.png" alt="vibe coded RSC — entire tree marked use client, same bundle as before, full complexity tax for zero gain" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  When You Don't Need Server Components
&lt;/h2&gt;

&lt;p&gt;Dan Abramov and Joe Savona discussed the honest tradeoffs of RSC in their live stream with Kent C. Dodds. The team has never claimed RSC is the right tool for every app.&lt;/p&gt;

&lt;p&gt;If your app is mostly interactive — drawing tools, real-time editors, complex forms — it consists almost entirely of Client Components anyway. RSC adds architectural complexity for minimal gain. If your API and frontend run in the same region, the round-trip savings from server-side fetching are negligible. The complexity isn't justified by the performance return.&lt;/p&gt;

&lt;p&gt;The more important consideration is team understanding. The React2Shell vulnerability was possible because the boundary between trusted server code and untrusted client input was blurry in the mental model. A team that treats Server Actions as internal functions will skip input validation — and that's not a performance problem, it's a critical security bug.&lt;/p&gt;

&lt;p&gt;And if you're adopting RSC because it's what the new Next.js docs show, stop and ask what problem you're solving. The React Labs blog post frames RSC as combining the simple request/response model of multi-page apps with the interactivity of single-page apps. That's a genuine win for content-heavy apps with complex data requirements. For a CRUD app with a fast API and a small bundle, you may be adding architectural weight to solve a problem you don't have.&lt;/p&gt;

&lt;p&gt;Measure the actual loading problem first. Understand the tradeoff. Then decide.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One-Sentence Rule
&lt;/h2&gt;

&lt;p&gt;Use Server Components when you have a measured loading problem caused by JavaScript bundle size or data waterfalls — and your team treats every Server Action as a public HTTP endpoint that requires the same validation discipline as any API route. In every other case, measure the problem first.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 9
&lt;/h2&gt;

&lt;p&gt;Part 9 is the last article in the series. It answers the question Part 1 opened with: &lt;em&gt;"With vast differences in computing power and network speed, how do we deliver the best user experience for everyone?"&lt;/em&gt; — pulling every piece of the series together into one complete picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dan Abramov — &lt;a href="https://github.com/reactwg/server-components/discussions/5" rel="noopener noreferrer"&gt;RSC from Scratch (GitHub Discussion)&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The canonical technical deep dive — inventing RSC from first principles. The RSC payload format and the streaming architecture described in this article come directly from this document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dan Abramov + Joe Savona — &lt;a href="https://www.youtube.com/watch?v=h7tur48JSaw" rel="noopener noreferrer"&gt;RSC Live Stream with Kent C. Dodds&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Honest discussion of RSC tradeoffs, limitations, and the mental model shift required. The source for the "when you don't need RSC" framing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=V3Z_mJJjwPs&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=27" rel="noopener noreferrer"&gt;How basic hydration works internally in React?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;tryToClaimNextHydratableInstance&lt;/code&gt;, &lt;code&gt;fiber.stateNode&lt;/code&gt; matching, and cursor-based DOM walking described in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=7mSl7-OBfY8&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=30" rel="noopener noreferrer"&gt;What is Progressive Hydration and how does it work internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Streaming HTML in chunks, Suspense comment node markers (&lt;code&gt;&amp;lt;!--$--&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;!--$!--&amp;gt;&lt;/code&gt;), and the retry callback mechanism.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dan Abramov&lt;/strong&gt; — &lt;a href="https://github.com/reactwg/server-components/discussions/5" rel="noopener noreferrer"&gt;RSC from Scratch (Part 1)&lt;/a&gt; written by &lt;code&gt;@gaearon&lt;/code&gt; on the reactwg GitHub. The PHP origin story, the RSC payload format, the streaming architecture, and the quote about JSX chunks all come directly from this document.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The React Team&lt;/strong&gt; — &lt;a href="https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023" rel="noopener noreferrer"&gt;React Labs: March 2023&lt;/a&gt; — the official Server Components definition quoted directly. &lt;a href="https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components" rel="noopener noreferrer"&gt;Critical Security Vulnerability in React Server Components&lt;/a&gt; — the official React2Shell disclosure, the vulnerability overview, and the timeline all come from the react.dev blog post.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lachlan Davidson&lt;/strong&gt; — for discovering and responsibly reporting CVE-2025-55182 to Meta's Bug Bounty program.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — the hydration mechanics (&lt;code&gt;tryToClaimNextHydratableInstance&lt;/code&gt;, &lt;code&gt;nextHydratableInstance&lt;/code&gt; cursor, fiber-to-DOM matching, selective hydration with Lanes priority, streaming with comment node markers) all come from JSer's source-level analysis: &lt;a href="https://jser.dev/react/2023/03/17/how-does-hydration-work-in-react/" rel="noopener noreferrer"&gt;How basic hydration works&lt;/a&gt;, &lt;a href="https://jser.dev/react/2023/03/27/hydration-with-suspense/" rel="noopener noreferrer"&gt;How hydration works with Suspense&lt;/a&gt;, &lt;a href="https://jser.dev/react/2023/03/30/progressive-hydration/" rel="noopener noreferrer"&gt;What is Progressive Hydration&lt;/a&gt;, and &lt;a href="https://jser.dev/react/2023/04/10/guess-rsc/" rel="noopener noreferrer"&gt;My guess at how RSC works&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unit 42 / Palo Alto Networks&lt;/strong&gt; — for the post-exploitation analysis of CVE-2025-55182 documenting real-world attack patterns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dan Abramov + Joe Savona&lt;/strong&gt; — &lt;a href="https://kentcdodds.com/blog/rsc-with-dan-abramov-and-joe-savona-live-stream" rel="noopener noreferrer"&gt;RSC Live Stream with Kent C. Dodds&lt;/a&gt; — for the honest discussion of RSC tradeoffs that informed the "when you don't need it" section.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 9 is the final article — the full picture, from Part 1's question to the complete answer. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt; &lt;code&gt;#security&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>rsc</category>
      <category>hydration</category>
    </item>
    <item>
      <title>How React Works (Part 7)? The Trap of Vibe Coding `useCallback`</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 14:15:49 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-7-the-trap-of-vibe-coding-usecallback-25cg</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-7-the-trap-of-vibe-coding-usecallback-25cg</guid>
      <description>&lt;h2&gt;
  
  
  The Trap of Vibe Coding &lt;code&gt;useCallback&lt;/code&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok"&gt;The Idea That Makes Suspense Possible&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4"&gt;The React Lifecycle From the Inside&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-6-how-state-actually-works-usestate-from-the-inside-1on"&gt;How State Actually Works&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1–6 first — especially Parts 3 and 6.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where We Left Off
&lt;/h2&gt;

&lt;p&gt;In Part 6 we saw how &lt;code&gt;useState&lt;/code&gt; works — state stored on the Fiber as a hook object, updates queued and processed on the next render, the component function running fresh from the top every time. Every render. From the top.&lt;/p&gt;

&lt;p&gt;That last part is what this article is about. Because every render from the top means every value inside the component is recreated. And that single fact is the root cause of most React performance problems — and the reason the most common advice for fixing them often makes things worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  You Wrapped Everything in &lt;code&gt;useCallback&lt;/code&gt;. You Added &lt;code&gt;React.memo&lt;/code&gt; to Every Component. Your App Is Still Slow. What Went Wrong?
&lt;/h2&gt;

&lt;p&gt;This is the scene. You open the React DevTools Profiler. Components are re-rendering on every interaction. You've read the articles. You know the tools. So you do what everyone does:&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;function&lt;/span&gt; &lt;span class="nf"&gt;SearchPage&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="dl"&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;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFilters&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;handleSearch&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&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;handleFilter&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&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;setFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;val&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;handleReset&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setFilters&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;handleExport&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;exportResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filters&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;activeFilters&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&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;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filters&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="nc"&gt;ResultList&lt;/span&gt;
      &lt;span class="na"&gt;onSearch&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSearch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onFilter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleFilter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onReset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleReset&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onExport&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleExport&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;activeFilters&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeFilters&lt;/span&gt;&lt;span class="si"&gt;}&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ResultList&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="nx"&gt;onSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onReset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeFilters&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;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You deploy. You open the Profiler again. &lt;code&gt;ResultList&lt;/code&gt; is still re-rendering. Some interactions are slower than before.&lt;/p&gt;

&lt;p&gt;What went wrong?&lt;/p&gt;

&lt;p&gt;This article answers that — from the inside out.&lt;/p&gt;




&lt;h2&gt;
  
  
  React's Default: Re-render Everything, Skip What It Can
&lt;/h2&gt;

&lt;p&gt;React's mental model has always been simple: when state changes, re-render. Not just the component that changed — all of its children too.&lt;/p&gt;

&lt;p&gt;But React has a built-in bailout system we covered in Part 3. When React walks down to a child, it checks one thing before running it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are the props the same object reference as last render?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes — same reference, not just same values — React skips that component entirely. The child doesn't run. Its children don't run. The entire subtree is skipped in one check. Free, automatic, no tools required.&lt;/p&gt;

&lt;p&gt;The problem: in practice, props almost always get new references on every render — even when the values inside them are identical. And this is the root cause of most React performance problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Render Creates New References Without You Noticing
&lt;/h2&gt;

&lt;p&gt;When a parent component re-renders, it runs its function from the top. Every line executes again:&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;function&lt;/span&gt; &lt;span class="nf"&gt;SearchPage&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ❌ New function object on every render&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSearch&lt;/span&gt; &lt;span class="o"&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ❌ New object on every render&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// ❌ New array on every render&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&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="nc"&gt;ResultList&lt;/span&gt; &lt;span class="na"&gt;onSearch&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSearch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="si"&gt;}&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;Every render, &lt;code&gt;handleSearch&lt;/code&gt; is a brand new function. &lt;code&gt;style&lt;/code&gt; is a brand new object. &lt;code&gt;results&lt;/code&gt; is a brand new array. They look identical — same values, same contents — but they're different objects in memory.&lt;/p&gt;

&lt;p&gt;React's bailout uses &lt;code&gt;===&lt;/code&gt; reference equality. Old &lt;code&gt;handleSearch !== new handleSearch&lt;/code&gt;. So React can't bail out, and &lt;code&gt;ResultList&lt;/code&gt; re-renders on every keystroke in the search input — even when the results haven't changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jvg5oolwy91clxq8pso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jvg5oolwy91clxq8pso.png" alt="parent re-renders → new references created → child props fail === check → child re-renders" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part Nobody Tells You: Children Re-render Even With No Props
&lt;/h2&gt;

&lt;p&gt;Here's the insight from jser.dev's bailout article that most performance guides miss entirely.&lt;/p&gt;

&lt;p&gt;Given this structure:&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;function&lt;/span&gt; &lt;span class="nf"&gt;SearchPage&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="dl"&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;div&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;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&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;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="si"&gt;}&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="nc"&gt;ResultList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* no props at all */&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user types and &lt;code&gt;SearchPage&lt;/code&gt; re-renders, &lt;code&gt;ResultList&lt;/code&gt; re-renders too — even though it receives zero props.&lt;/p&gt;

&lt;p&gt;Why? Because when &lt;code&gt;SearchPage&lt;/code&gt;'s function runs, it creates a &lt;strong&gt;new React element&lt;/strong&gt; for &lt;code&gt;&amp;lt;ResultList /&amp;gt;&lt;/code&gt;. That element object is new. When React's reconciler compares the old &lt;code&gt;pendingProps&lt;/code&gt; to the new &lt;code&gt;pendingProps&lt;/code&gt; on the &lt;code&gt;ResultList&lt;/code&gt; fiber, they're different objects. So React re-renders &lt;code&gt;ResultList&lt;/code&gt;. The issue isn't the prop values — it's where the element is created.&lt;/p&gt;

&lt;p&gt;This means &lt;code&gt;React.memo&lt;/code&gt; on &lt;code&gt;ResultList&lt;/code&gt; alone doesn't help here at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Free Fix: Children as Props
&lt;/h2&gt;

&lt;p&gt;This is the most underused performance pattern in React. Zero memoization. Zero dependency arrays. Just a structural change.&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;// ❌ Before — ResultList re-renders whenever SearchPage re-renders&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchPage&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="dl"&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;div&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;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&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;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="si"&gt;}&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="nc"&gt;ResultList&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;div&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;span class="c1"&gt;// ✅ After — ResultList never re-renders when query changes&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="nc"&gt;SearchPage&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="nc"&gt;ResultList&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="nc"&gt;SearchPage&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="dl"&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;div&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;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&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;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="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;children&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this work? The &lt;code&gt;&amp;lt;ResultList /&amp;gt;&lt;/code&gt; element is now created inside &lt;code&gt;App&lt;/code&gt;, not inside &lt;code&gt;SearchPage&lt;/code&gt;. When &lt;code&gt;SearchPage&lt;/code&gt;'s state changes, &lt;code&gt;App&lt;/code&gt; doesn't re-render. The &lt;code&gt;children&lt;/code&gt; prop that &lt;code&gt;SearchPage&lt;/code&gt; receives is the same object reference as before. React's built-in bailout kicks in — same reference, skip &lt;code&gt;ResultList&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;React.memo&lt;/code&gt;. No &lt;code&gt;useCallback&lt;/code&gt;. No overhead. Just structure.&lt;/p&gt;

&lt;p&gt;The React docs state this directly: &lt;em&gt;"When a component visually wraps other components, let it accept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don't need to re-render."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Always try structural fixes before reaching for memoization tools. They're free and they don't break.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8futd3e6g8vr11ynisk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8futd3e6g8vr11ynisk.png" alt="ResultList as children — App creates element, SearchPage renders children prop, same reference → bailout" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  When You Actually Need &lt;code&gt;React.memo&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes the structural fix isn't possible. The child genuinely needs to be defined inside the parent and receive real props. That's when &lt;code&gt;React.memo&lt;/code&gt; enters.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;React.memo&lt;/code&gt; wraps your component in a &lt;code&gt;MemoComponent&lt;/code&gt; fiber. Instead of checking reference equality on the whole props object, React runs &lt;code&gt;shallowEqual&lt;/code&gt; — comparing each individual prop value:&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;ResultList&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="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onSearch&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="k"&gt;return&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;results&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;r&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;Result&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;r&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;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;r&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;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;If &lt;code&gt;results&lt;/code&gt; and &lt;code&gt;onSearch&lt;/code&gt; have the same values as last time, React bails out. Even if the props object itself is new.&lt;/p&gt;

&lt;p&gt;One internal detail worth knowing: when you wrap a simple function component with no custom compare function and no &lt;code&gt;defaultProps&lt;/code&gt;, React silently upgrades the fiber tag from &lt;code&gt;MemoComponent&lt;/code&gt; to &lt;code&gt;SimpleMemoComponent&lt;/code&gt; — a faster internal optimization path. Add a custom compare function and you lose this fast path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But &lt;code&gt;React.memo&lt;/code&gt; alone almost never works.&lt;/strong&gt; If any prop is a function or object created inline during render, &lt;code&gt;shallowEqual&lt;/code&gt; fails on that prop — new reference every time. &lt;code&gt;React.memo&lt;/code&gt; bails out on nothing.&lt;/p&gt;

&lt;p&gt;This is why in the opening example, &lt;code&gt;ResultList&lt;/code&gt; still re-renders even though it's wrapped in &lt;code&gt;React.memo&lt;/code&gt;. &lt;code&gt;handleSearch&lt;/code&gt;, &lt;code&gt;handleFilter&lt;/code&gt;, &lt;code&gt;handleReset&lt;/code&gt;, &lt;code&gt;handleExport&lt;/code&gt; — all created inline, all new references every render. &lt;code&gt;React.memo&lt;/code&gt; sees four changed props and re-renders anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; Actually Do — From the Source
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; exist for one reason: to stabilize references so &lt;code&gt;React.memo&lt;/code&gt; can do its job.&lt;/p&gt;

&lt;p&gt;Here's the actual source code for both hooks — &lt;code&gt;mountMemo&lt;/code&gt; on initial render and &lt;code&gt;updateMemo&lt;/code&gt; on re-render:&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;// From React source — initial render&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mountMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deps&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;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mountWorkInProgressHook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// create hook in linked list&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;span class="nx"&gt;deps&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;nextValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextCreate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          &lt;span class="c1"&gt;// run the factory fn&lt;/span&gt;
  &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoizedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// store [value, deps]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// From React source — every re-render&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deps&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;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;updateWorkInProgressHook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// walk to this hook in linked list&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;span class="nx"&gt;deps&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;prevState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoizedState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// [prevValue, prevDeps]&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;prevState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&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;prevDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="nf"&gt;areHookInputsEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextDeps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prevDeps&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;prevState&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="c1"&gt;// deps unchanged → return cached value&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;nextValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextCreate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;         &lt;span class="c1"&gt;// deps changed → recompute&lt;/span&gt;
  &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoizedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&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;nextValue&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;updateCallback&lt;/code&gt; is identical — the only difference is it stores the function itself instead of calling it.&lt;/p&gt;

&lt;p&gt;The dependency comparison runs through &lt;code&gt;areHookInputsEqual&lt;/code&gt;:&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;// From React source — runs on every render for every hook&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;areHookInputsEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextDeps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prevDeps&lt;/span&gt;&lt;span class="p"&gt;)&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;prevDeps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nextDeps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextDeps&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;prevDeps&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;continue&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// one mismatch → recompute&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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 loop runs on &lt;strong&gt;every render, for every hook, for every dependency&lt;/strong&gt;. It doesn't matter whether deps changed or not — the loop always runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Cost: What Happens on Every Render
&lt;/h2&gt;

&lt;p&gt;This is what the articles don't tell you.&lt;/p&gt;

&lt;p&gt;Even when dependencies haven't changed and &lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;useMemo&lt;/code&gt; return the cached value, React still does this on every render:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Walks to this hook in the linked list&lt;/strong&gt; — &lt;code&gt;updateWorkInProgressHook()&lt;/code&gt; traverses the hook chain every time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads &lt;code&gt;hook.memoizedState&lt;/code&gt;&lt;/strong&gt; — accesses &lt;code&gt;[prevValue, prevDeps]&lt;/code&gt; from memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs &lt;code&gt;areHookInputsEqual&lt;/code&gt;&lt;/strong&gt; — loops through every dependency with &lt;code&gt;Object.is&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocates a new function&lt;/strong&gt; (for &lt;code&gt;useCallback&lt;/code&gt;) — React receives the new &lt;code&gt;() =&amp;gt; fn&lt;/code&gt; you wrote, then decides whether to discard it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point surprises most developers. When you write:&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;handleSearch&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript creates the arrow function &lt;code&gt;() =&amp;gt; search(query)&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; calling &lt;code&gt;useCallback&lt;/code&gt;. React receives it, runs &lt;code&gt;areHookInputsEqual&lt;/code&gt;, and if deps haven't changed, discards the new function and returns the old one. The allocation happened regardless.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;useCallback(fn, [a, b, c])&lt;/code&gt; with unchanged deps on every render means: one function allocation (discarded), three &lt;code&gt;Object.is&lt;/code&gt; comparisons, one linked list traversal. For a component that re-renders 50 times per second during a scroll, that's 150 comparisons per second — for a component that might have been fast to render in 0.1ms anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjtjfxgqxetd8jxbs2qc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjtjfxgqxetd8jxbs2qc.png" alt="useCallback hidden cost — every render: allocate fn → walk hook list → compare deps → return old fn" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vibe Coding Trap
&lt;/h2&gt;

&lt;p&gt;Here's what actually happens in most codebases.&lt;/p&gt;

&lt;p&gt;Someone reads that &lt;code&gt;useCallback&lt;/code&gt; prevents re-renders. They add it to every function. They add &lt;code&gt;React.memo&lt;/code&gt; to every component. They add &lt;code&gt;useMemo&lt;/code&gt; to every derived value. "Just to be safe." Copy-paste from the last project. Vibe coding.&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;// Real pattern from real codebases&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfileCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&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;handleMouseEnter&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHovered&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="p"&gt;[]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseLeave&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHovered&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleFocus&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setFocused&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="p"&gt;[]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleBlur&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setFocused&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;containerClass&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="s2"&gt;`card &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hovered&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card--hovered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&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; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;focused&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card--focused&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&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;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hovered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;focused&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;div&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;containerClass&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onMouseEnter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleMouseEnter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onMouseLeave&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleMouseLeave&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onFocus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleFocus&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleBlur&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;UserAvatar&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="si"&gt;}&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;div&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;This is a card component. It renders in under 0.1ms. Nothing inside it is expensive. &lt;code&gt;UserAvatar&lt;/code&gt; doesn't receive any of the memoized values. The &lt;code&gt;containerClass&lt;/code&gt; string is trivially fast to compute.&lt;/p&gt;

&lt;p&gt;Every single &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; here adds overhead — dependency comparisons, hook traversals, function allocations — for zero performance benefit. The component was never the bottleneck.&lt;/p&gt;

&lt;p&gt;And the real cost shows up in debugging. When &lt;code&gt;containerClass&lt;/code&gt; produces the wrong value, you now have to trace through &lt;code&gt;useMemo&lt;/code&gt; and its &lt;code&gt;[hovered, focused]&lt;/code&gt; deps to understand why. When &lt;code&gt;handleFocus&lt;/code&gt; behaves unexpectedly, you check the &lt;code&gt;useCallback&lt;/code&gt; closure. What was a simple state bug becomes a memoization debugging session.&lt;/p&gt;

&lt;p&gt;The React docs are direct about this: &lt;em&gt;"There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside of this approach is that code becomes less readable. Also, not all memoization is effective: a single value that's 'always new' is enough to break memoization for an entire component."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Right Order
&lt;/h2&gt;

&lt;p&gt;Here's the order that actually fixes performance problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Measure first.&lt;/strong&gt; Open React DevTools Profiler. Record a session. Find the component that's actually slow — its render time, how often it renders, and why. Don't optimize what you haven't measured. Most of the time the bottleneck is not where you think it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55qqhmufz4nz5xriyaco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55qqhmufz4nz5xriyaco.png" alt="Profiler flame graph — before children-as-props: ResultList lights up on every keystroke / after: ResultList stays dark, only SearchPage re-renders" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fix the structure.&lt;/strong&gt; Can the slow child's element be created in a parent that doesn't re-render? Can state be moved down closer to where it's used? Can you use the children-as-props pattern? These fixes are free — no memoization overhead, no stale closure risk, no dependency arrays to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Memoize if structure can't fix it.&lt;/strong&gt; If structural fixes aren't possible and the component is genuinely expensive to render and receives props that could be stable — then reach for &lt;code&gt;React.memo&lt;/code&gt; + &lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;useMemo&lt;/code&gt; together. All three are needed. &lt;code&gt;React.memo&lt;/code&gt; without stable references does nothing. &lt;code&gt;useCallback&lt;/code&gt; without &lt;code&gt;React.memo&lt;/code&gt; on the child does nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Verify it worked.&lt;/strong&gt; Re-run the Profiler. Confirm the component no longer re-renders unnecessarily. If it still does, find what reference is still unstable and trace it back.&lt;/p&gt;

&lt;p&gt;The rule, in one sentence: &lt;strong&gt;don't add memoization until you have a specific, measured component that re-renders too often, with expensive renders, and props that can be stabilized.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on the React Compiler
&lt;/h2&gt;

&lt;p&gt;The React Compiler (previously React Forget) is now stable. It's a build-time Babel plugin that automatically applies memoization where it's safe — analyzing your components, inferring dependencies, and inserting optimizations without you writing any of it.&lt;/p&gt;

&lt;p&gt;But it only works on components that follow the Rules of React: pure renders, no prop mutation, no non-deterministic values during render. Code that follows these rules gets automatic optimization. Code that doesn't gets de-opted — the Compiler falls back and does nothing.&lt;/p&gt;

&lt;p&gt;Here's the important point: understanding everything in this article — why references matter, why structural fixes come first, why &lt;code&gt;useCallback&lt;/code&gt; has a cost — is exactly what you need to write code the Compiler can optimize. The Compiler doesn't replace understanding React's rendering model. It rewards you for having it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 8
&lt;/h2&gt;

&lt;p&gt;In Part 8 we go into Server Components and Hydration — how React renders on the server, streams the result to the client, and hands off interactivity without re-doing all the work. The rendering model we've built throughout this series extends to the server in some surprising ways.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=LfwMlGjiaW0&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=13" rel="noopener noreferrer"&gt;How does React bailout work in reconciliation?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The source for the "children re-render even with no props" insight and the children-as-props bailout pattern. The demo shows exactly which components re-render and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=0jbV6apamhs&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=14" rel="noopener noreferrer"&gt;How does React.memo() work internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;MemoComponent&lt;/code&gt; vs &lt;code&gt;SimpleMemoComponent&lt;/code&gt; internal distinction and &lt;code&gt;shallowEqual&lt;/code&gt; — the source for the React.memo internals section.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — the "children re-render even with no props" insight and the children-as-props bailout technique come directly from &lt;a href="https://jser.dev/react/2022/01/07/how-does-bailout-work/" rel="noopener noreferrer"&gt;How does React bailout work?&lt;/a&gt;. The &lt;code&gt;MemoComponent&lt;/code&gt;/&lt;code&gt;SimpleMemoComponent&lt;/code&gt; distinction from &lt;a href="https://jser.dev/react/2022/01/11/how-react-memo-works/" rel="noopener noreferrer"&gt;How does React.memo() work internally?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React source&lt;/strong&gt; — &lt;code&gt;mountMemo&lt;/code&gt;, &lt;code&gt;updateMemo&lt;/code&gt;, &lt;code&gt;mountCallback&lt;/code&gt;, &lt;code&gt;updateCallback&lt;/code&gt;, and &lt;code&gt;areHookInputsEqual&lt;/code&gt; are taken directly from &lt;code&gt;packages/react-reconciler/src/ReactFiberHooks.js&lt;/code&gt; in the &lt;a href="https://github.com/facebook/react" rel="noopener noreferrer"&gt;facebook/react&lt;/a&gt; repo. Verified via multiple source walkthroughs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React docs&lt;/strong&gt; — the children-as-props principle and the "not all memoization is effective" quote come directly from &lt;a href="https://react.dev/reference/react/memo" rel="noopener noreferrer"&gt;react.dev/reference/react/memo&lt;/a&gt;. The React Compiler section draws from &lt;a href="https://react.dev/learn/react-compiler/introduction" rel="noopener noreferrer"&gt;react.dev/learn/react-compiler&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for JavaScript visualizations that shaped this series' style.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 8 is next — Server Components and Hydration: how React moved rendering to the server without breaking the client model. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#performance&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>hooks</category>
      <category>usecallback</category>
    </item>
    <item>
      <title>How React Works (Part 6)? How State Actually Works: useState from the Inside</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 13:59:40 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-6-how-state-actually-works-usestate-from-the-inside-1on</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-6-how-state-actually-works-usestate-from-the-inside-1on</guid>
      <description>&lt;h2&gt;
  
  
  How State Actually Works: useState from the Inside
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok"&gt;The Idea That Makes Suspense Possible&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4"&gt;The React Lifecycle From the Inside&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1–5 first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Three Things That Confuse Almost Everyone
&lt;/h2&gt;

&lt;p&gt;Here are three behaviors of &lt;code&gt;useState&lt;/code&gt; that trip up developers at every level:&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;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&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;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&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="mi"&gt;0&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;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// You might expect count to become 3&lt;/span&gt;
    &lt;span class="c1"&gt;// It becomes 1&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;handleLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// You might expect to see the new value&lt;/span&gt;
    &lt;span class="c1"&gt;// You see the old value&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;handleSame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// setting same value&lt;/span&gt;
    &lt;span class="c1"&gt;// You might expect no re-render&lt;/span&gt;
    &lt;span class="c1"&gt;// Sometimes React still re-renders once&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;All three behaviors make complete sense once you understand how &lt;code&gt;useState&lt;/code&gt; actually works under the hood. This article explains all three — and by the end, you'll never be surprised by state again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where State Lives
&lt;/h2&gt;

&lt;p&gt;The most important question to start with: when you call &lt;code&gt;useState(0)&lt;/code&gt;, where does that &lt;code&gt;0&lt;/code&gt; actually live?&lt;/p&gt;

&lt;p&gt;Not in the component function. Component functions are just regular JavaScript functions — they have no persistent memory between calls. Every time React re-renders your component, it calls the function fresh from the top.&lt;/p&gt;

&lt;p&gt;State lives on the &lt;strong&gt;Fiber&lt;/strong&gt;. Specifically, on a hook object stored in the fiber's &lt;code&gt;memoizedState&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Every hook call creates a new hook object and appends it to a linked list hanging off the fiber:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Counter fiber
  └─ memoizedState → hook (useState count)
                       └─ next → hook (useEffect)
                                   └─ next → hook (useRef)
                                               └─ next → null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each hook object stores the current state value in its own &lt;code&gt;memoizedState&lt;/code&gt; field, plus an &lt;code&gt;updateQueue&lt;/code&gt; for pending updates, plus a link to the next hook.&lt;/p&gt;

&lt;p&gt;This is why the rules of hooks exist — specifically why you can't call hooks conditionally. React doesn't track hooks by name. It tracks them by position in the linked list. Call number 1 is always &lt;code&gt;useState count&lt;/code&gt;, call number 2 is always &lt;code&gt;useEffect&lt;/code&gt;, and so on. If you skip a hook call with an &lt;code&gt;if&lt;/code&gt; statement, every subsequent hook gets the wrong state from the wrong position.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswfqc3xdpr9hwqqpdvh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswfqc3xdpr9hwqqpdvh2.png" alt="fiber memoizedState linked list — hook objects in order"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens When You Call &lt;code&gt;setState&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's what most developers picture when they call &lt;code&gt;setCount(count + 1)&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React updates the count, then re-renders the component.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's what actually happens:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React creates a small update object and adds it to a queue. Then it schedules a re-render for later. The component function does not run again right now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. Calling &lt;code&gt;setState&lt;/code&gt; is not synchronous. It's not instant. It's closer to leaving a note — "please re-render this component with this new value when you get a chance."&lt;/p&gt;

&lt;p&gt;From jser.dev's useState article, here is the actual sequence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Create an update object.&lt;/strong&gt; React packages your new value (or updater function) into a small object that also carries the current priority lane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Stash it in a queue.&lt;/strong&gt; The update goes into a global buffer (&lt;code&gt;concurrentQueues&lt;/code&gt;). It's not attached to the fiber yet — React is potentially in the middle of rendering something else and can't safely modify the fiber tree right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Schedule a re-render.&lt;/strong&gt; React calls &lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt;, which walks up to the root, marks the trail of &lt;code&gt;childLanes&lt;/code&gt;, and registers a task with the Scheduler (from Part 2). The Scheduler will call back to run the render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — At the start of the next render, attach updates.&lt;/strong&gt; Before the render begins, &lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt; runs and attaches all stashed updates from the global buffer to their respective fibers' queues. Now the fiber is ready to process them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — During render, process the queue.&lt;/strong&gt; When React processes the Counter fiber, it reads &lt;code&gt;hook.queue&lt;/code&gt;, processes the pending updates in order, and the result becomes the new &lt;code&gt;hook.memoizedState&lt;/code&gt; — the new value that &lt;code&gt;useState&lt;/code&gt; will return for this render.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj10h32xxxvkkihk1uit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj10h32xxxvkkihk1uit.png" alt="setState journey — update object → concurrentQueues → scheduleUpdateOnFiber → Scheduler → render → process queue → new state"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Concrete Trace: One Button Click
&lt;/h3&gt;

&lt;p&gt;Let's make this real. The Counter from the opening — &lt;code&gt;count&lt;/code&gt; starts at &lt;code&gt;0&lt;/code&gt;, user clicks the button:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The click happens.&lt;/strong&gt; The browser fires a click event. React's synthetic event handler calls your &lt;code&gt;handleClick&lt;/code&gt;, which calls &lt;code&gt;setCount(count + 1)&lt;/code&gt; — that's &lt;code&gt;setCount(1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dispatchSetState&lt;/code&gt; runs.&lt;/strong&gt; React creates an update object &lt;code&gt;{ action: 1, lane: SyncLane }&lt;/code&gt; and pushes it into the global &lt;code&gt;concurrentQueues&lt;/code&gt; buffer. The Counter fiber is unchanged — it still has &lt;code&gt;hook.memoizedState = 0&lt;/code&gt;. The component function has not run again yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt; runs.&lt;/strong&gt; React walks up from the Counter fiber to the root, marking &lt;code&gt;childLanes&lt;/code&gt; on every ancestor. The Scheduler registers a task to call &lt;code&gt;performConcurrentWorkOnRoot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The event handler finishes.&lt;/strong&gt; React checks for other queued updates — there are none. Since this was a user interaction (&lt;code&gt;SyncLane&lt;/code&gt;), React runs the render synchronously rather than waiting for the next macro task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt; runs.&lt;/strong&gt; The pending update is moved from the global buffer and attached to &lt;code&gt;hook.queue.pending&lt;/code&gt; on the Counter fiber. The fiber is now ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React renders the Counter fiber.&lt;/strong&gt; &lt;code&gt;updateState&lt;/code&gt; reads &lt;code&gt;hook.queue&lt;/code&gt;, finds the pending update &lt;code&gt;{ action: 1 }&lt;/code&gt;, runs it through &lt;code&gt;basicStateReducer&lt;/code&gt;, and gets &lt;code&gt;1&lt;/code&gt;. This becomes the new &lt;code&gt;hook.memoizedState&lt;/code&gt;. The component function runs with &lt;code&gt;count = 1&lt;/code&gt; and returns new JSX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit phase.&lt;/strong&gt; React finds the changed text node and updates &lt;code&gt;button.textContent&lt;/code&gt; to &lt;code&gt;"click 1"&lt;/code&gt;. The DOM reflects the new state.&lt;/p&gt;

&lt;p&gt;Total time from click to DOM update: a few milliseconds. Total re-renders: exactly one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why You See the Old Value After &lt;code&gt;setState&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now the first confusion makes sense.&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="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// still the old value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;setCount&lt;/code&gt; didn't change &lt;code&gt;count&lt;/code&gt;. It created an update object and scheduled a re-render. The variable &lt;code&gt;count&lt;/code&gt; is a local variable in the current function call — it was captured when the component rendered and it will never change during this render. The new value only exists after the next render completes and &lt;code&gt;useState&lt;/code&gt; reads from the processed queue.&lt;/p&gt;

&lt;p&gt;This is a fundamental property of React's model: &lt;strong&gt;state values are snapshots.&lt;/strong&gt; Every render captures its own version of state, and that snapshot is frozen for the duration of that render.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Three &lt;code&gt;setCount&lt;/code&gt; Calls Only Increment Once
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// count goes from 0 to 1, not 0 to 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these three calls captures the same &lt;code&gt;count&lt;/code&gt; — the snapshot from the current render, which is &lt;code&gt;0&lt;/code&gt;. So all three are equivalent to &lt;code&gt;setCount(0 + 1)&lt;/code&gt;. React queues three updates, all with value &lt;code&gt;1&lt;/code&gt;. When it processes them in the next render, the result is &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is the updater function form:&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="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// count goes from 0 to 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you pass a function, React processes the updates in sequence — each one receives the result of the previous. The queue runs &lt;code&gt;0 → 1 → 2 → 3&lt;/code&gt;. The updater function form is specifically designed for when you need to chain updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Multiple &lt;code&gt;setState&lt;/code&gt; Calls Don't Cause Multiple Re-renders
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;batching&lt;/strong&gt; — and it's one of React's most important performance features.&lt;/p&gt;

&lt;p&gt;When you call &lt;code&gt;setState&lt;/code&gt; multiple times in the same event handler, React doesn't re-render after each call. It collects all the updates, then does a single re-render at the end.&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;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// queued&lt;/span&gt;
  &lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// queued&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="c1"&gt;// queued&lt;/span&gt;
  &lt;span class="c1"&gt;// → one re-render, not three&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From jser.dev's useState article: updates are stashed in the global &lt;code&gt;concurrentQueues&lt;/code&gt; buffer and only attached to fibers at the beginning of the next render via &lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt;. This means all three &lt;code&gt;setState&lt;/code&gt; calls during the same event handler land in the queue before any render begins — React processes them all in one pass.&lt;/p&gt;

&lt;p&gt;Before React 18, batching only happened inside React event handlers. In &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt; callbacks, or native event handlers, each &lt;code&gt;setState&lt;/code&gt; would trigger a separate re-render. React 18 introduced &lt;strong&gt;automatic batching&lt;/strong&gt; — batching now happens everywhere by default, regardless of where the update originates.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Same-Value Optimization (and Its Catch)
&lt;/h2&gt;

&lt;p&gt;What happens when you call &lt;code&gt;setState&lt;/code&gt; with the same value the state already has?&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="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// count is already 5, setting it to 5 again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React tries to skip the re-render entirely. From jser.dev's article: before scheduling the re-render, &lt;code&gt;dispatchSetState&lt;/code&gt; eagerly computes the new state and compares it to the current state using &lt;code&gt;Object.is&lt;/code&gt;. If they're identical, React calls &lt;code&gt;enqueueConcurrentHookUpdateAndEagerlyBailout&lt;/code&gt; and returns immediately — no render is scheduled.&lt;/p&gt;

&lt;p&gt;But here's the catch from jser.dev: this bailout only happens when the fiber's update queue is currently empty. If there's other pending work on the component, React can't safely apply this optimization and may still re-render once. The comment in the React source code says "React tries to avoid scheduling re-render with best effort, but no guarantee."&lt;/p&gt;

&lt;p&gt;So the rule in practice: setting state to the same value usually prevents a re-render, but not always. Don't write code that depends on this optimization for correctness.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;useRef&lt;/code&gt;: State Without Re-renders
&lt;/h2&gt;

&lt;p&gt;Now that you understand how &lt;code&gt;useState&lt;/code&gt; works, &lt;code&gt;useRef&lt;/code&gt; becomes obvious.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useRef&lt;/code&gt; is implemented almost identically to &lt;code&gt;useState&lt;/code&gt; — it creates a hook object on the fiber's linked list, stores its value in &lt;code&gt;memoizedState&lt;/code&gt;. The difference: updating a ref doesn't create an update object and doesn't call &lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt;. It just mutates the &lt;code&gt;current&lt;/code&gt; property of the ref object directly.&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;ref&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ref&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// direct mutation — no queue, no schedule, no re-render&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because React never learns about the change, it never re-renders. The new value is available immediately (no snapshot problem), but React won't react to it. This makes &lt;code&gt;useRef&lt;/code&gt; perfect for values that need to persist across renders without triggering them — timer IDs, previous values, DOM node references.&lt;/p&gt;

&lt;p&gt;The tradeoff is exactly what you'd expect: refs are live, mutable, immediate — but invisible to React's rendering system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Picture
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; is not a magical React primitive. It's a hook object on a linked list on a fiber, with a queue for pending updates and a scheduler that processes them. Every behavior that seems surprising becomes logical once you see the structure:&lt;/p&gt;

&lt;p&gt;The snapshot problem exists because state is stored on the fiber, not in a mutable variable — and the fiber gets its new value only after the render processes the queue. The batching behavior exists because updates go into a global buffer before being attached to fibers. The same-value optimization exists because React checks eagerly before scheduling. And &lt;code&gt;useRef&lt;/code&gt; is simply the same structure without the scheduling step.&lt;/p&gt;

&lt;p&gt;State in React is not instant, synchronous, or mutable in the traditional sense. It's a description of what the next render should look like — and React processes that description on its own schedule, in its own order, using the Fiber and Scheduler machinery from Parts 2 and 3.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 7
&lt;/h2&gt;

&lt;p&gt;In Part 7 we look at performance — not the solutions first, but the problems. What actually causes unnecessary re-renders? Why does passing a new object as a prop matter? When is React doing work it doesn't need to? Understanding the problems clearly is what makes &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt; make sense — rather than things you add until the lag goes away.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=svaUEHMuv9w&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=5" rel="noopener noreferrer"&gt;How does useState() work internally in React?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The primary source for this entire article — &lt;code&gt;mountState&lt;/code&gt;, &lt;code&gt;dispatchSetState&lt;/code&gt;, &lt;code&gt;concurrentQueues&lt;/code&gt;, &lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt;, the eager bailout, and the batching mechanism. All sourced from here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=0GM-1W7i9Tk&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=3" rel="noopener noreferrer"&gt;How does React re-render internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
How the update queue is processed during the render phase — the other half of the state story.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — every mechanism in this article comes from JSer's source-level analysis of &lt;code&gt;useState&lt;/code&gt;. The &lt;code&gt;mountState&lt;/code&gt; flow, &lt;code&gt;dispatchSetState&lt;/code&gt; internals, &lt;code&gt;concurrentQueues&lt;/code&gt; global buffer, &lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt;, the eager same-value bailout with the "best effort, no guarantee" caveat, and the batching explanation all come directly from &lt;a href="https://jser.dev/2023-06-19-how-does-usestate-work/" rel="noopener noreferrer"&gt;How does useState() work internally in React?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React source&lt;/strong&gt; — &lt;code&gt;packages/react-reconciler/src/ReactFiberHooks.js&lt;/code&gt; (&lt;code&gt;mountState&lt;/code&gt;, &lt;code&gt;dispatchSetState&lt;/code&gt;, &lt;code&gt;mountWorkInProgressHook&lt;/code&gt;) and &lt;code&gt;packages/react-reconciler/src/ReactFiberConcurrentUpdates.js&lt;/code&gt; (&lt;code&gt;enqueueConcurrentHookUpdate&lt;/code&gt;, &lt;code&gt;finishQueueingConcurrentUpdates&lt;/code&gt;) in the &lt;a href="https://github.com/facebook/react" rel="noopener noreferrer"&gt;facebook/react&lt;/a&gt; repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for JavaScript visualizations that shaped this series' style.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 7 is next — performance: what actually causes unnecessary re-renders, before we reach for any optimization tools. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>hooks</category>
      <category>usestate</category>
    </item>
    <item>
      <title>How React Works (Part 5)? The React Lifecycle From the Inside: When Things Actually Run</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 13:03:02 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-5-the-react-lifecycle-from-the-inside-when-things-actually-run-oj4</guid>
      <description>&lt;h2&gt;
  
  
  The React Lifecycle From the Inside: When Things Actually Run
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok"&gt;The Idea That Makes Suspense Possible&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1–4 first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Puzzle Most React Developers Get Wrong
&lt;/h2&gt;

&lt;p&gt;Quick quiz. What order do these log?&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;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useLayoutEffect&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;layout effect&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;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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;render&lt;/span&gt;&lt;span class="dl"&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;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;Most people guess: render → effect → layout effect, or render → layout effect → effect.&lt;/p&gt;

&lt;p&gt;The answer is: &lt;strong&gt;render → layout effect → effect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But more importantly — &lt;em&gt;why?&lt;/em&gt; Why does &lt;code&gt;useLayoutEffect&lt;/code&gt; run before &lt;code&gt;useEffect&lt;/code&gt;? Why does &lt;code&gt;useEffect&lt;/code&gt; run at all after the component already returned? What does "after the browser paints" actually mean, and is that even always true?&lt;/p&gt;

&lt;p&gt;This article answers all of that. And by the end, you'll be able to look at any component and predict exactly when each piece of it runs — and why.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Phases of a React Render
&lt;/h2&gt;

&lt;p&gt;Before we get into effects, we need to remember the pipeline from Part 2. Every React update goes through three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render&lt;/strong&gt; — React runs your component functions, diffs the trees, figures out what changed. Nothing is written to the DOM yet. This is where &lt;code&gt;console.log('render')&lt;/code&gt; fires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt; — React takes everything it figured out during Render and applies it to the real DOM. This is synchronous and uninterruptible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Effects&lt;/strong&gt; — After the DOM is updated, React runs your effects.&lt;/p&gt;

&lt;p&gt;The key insight is that effects are &lt;em&gt;not&lt;/em&gt; part of rendering. They're a separate step that happens after the DOM is already updated. This is why you can safely read the DOM inside an effect — by the time it runs, the DOM reflects the current state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05tpi8uej7ozfaygozmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05tpi8uej7ozfaygozmz.png" alt="three phases — Render → Commit → Effects, with browser paint between Commit and Passive Effects" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;code&gt;useEffect&lt;/code&gt; Actually Is
&lt;/h2&gt;

&lt;p&gt;Here's the mental model most developers have: &lt;code&gt;useEffect&lt;/code&gt; is "code that runs after render." That's roughly right but missing the important details.&lt;/p&gt;

&lt;p&gt;Here's the more accurate model: &lt;strong&gt;&lt;code&gt;useEffect&lt;/code&gt; is a declaration that you have side effects to run after React is done with the DOM.&lt;/strong&gt; You're not scheduling a callback — you're telling React about work that needs to happen, and React decides when to run it.&lt;/p&gt;

&lt;p&gt;The distinction matters because React doesn't run effects immediately after commit. It &lt;em&gt;schedules&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;From jser.dev's lifecycle article — here's what actually happens:&lt;/p&gt;

&lt;p&gt;After the Commit phase finishes updating the DOM, React schedules your &lt;code&gt;useEffect&lt;/code&gt; callbacks as a separate task in the Scheduler's queue — the same Scheduler we covered in Part 2. That means effects run in a &lt;strong&gt;new macro task&lt;/strong&gt;, after the browser has had a chance to paint the updated screen.&lt;/p&gt;

&lt;p&gt;This is why the React docs say &lt;code&gt;useEffect&lt;/code&gt; runs "after the browser paints." The sequence looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Render phase — your component functions run
2. Commit phase — DOM is updated
3. Browser paints the screen
4. Scheduler fires — useEffect callbacks run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser gets step 3 because steps 1-2 are one macro task, and step 4 is a new macro task. Between any two macro tasks, the browser can paint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kpib2lhqyxsnu1ibbnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kpib2lhqyxsnu1ibbnz.png" alt="macro task timeline — render+commit → browser paint → new macro task → useEffect runs" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cleanup: Why It Runs Before the Next Effect
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;useEffect&lt;/code&gt; can return a cleanup function:&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="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;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;subscribe&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="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="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// cleanup&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;When &lt;code&gt;id&lt;/code&gt; changes and the effect needs to re-run, React runs the &lt;em&gt;cleanup of the previous effect first&lt;/em&gt;, then runs the new effect. Always in that order.&lt;/p&gt;

&lt;p&gt;This is also true on unmount — the cleanup runs when the component leaves the tree.&lt;/p&gt;

&lt;p&gt;The reason is straightforward: React never wants two instances of the same effect active at the same time. Before setting up the new subscription, it tears down the old one. This is React being deliberate about side effects — always clean up before you set up again.&lt;/p&gt;

&lt;p&gt;From jser.dev's effect lifecycle article: cleanups run first in &lt;code&gt;commitPassiveUnmountEffects&lt;/code&gt;, then new effects run in &lt;code&gt;commitPassiveMountEffects&lt;/code&gt;. Both happen in the same scheduled task, in the same order as the tree (children before parents, same as &lt;code&gt;completeWork&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;useLayoutEffect&lt;/code&gt;: The Synchronous Version
&lt;/h2&gt;

&lt;p&gt;Here's the critical difference: &lt;code&gt;useLayoutEffect&lt;/code&gt; runs &lt;strong&gt;synchronously inside the Commit phase&lt;/strong&gt;, before the browser paints.&lt;/p&gt;

&lt;p&gt;The sequence with &lt;code&gt;useLayoutEffect&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Render phase — component functions run
2. Commit phase — DOM is updated
3. useLayoutEffect callbacks run ← here, synchronously, before paint
4. Browser paints
5. useEffect callbacks run ← here, in next macro task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why &lt;code&gt;useLayoutEffect&lt;/code&gt; fires before &lt;code&gt;useEffect&lt;/code&gt; in our opening quiz — it's not "earlier in the same phase," it's in a completely different phase.&lt;/p&gt;

&lt;p&gt;And this is why &lt;code&gt;useLayoutEffect&lt;/code&gt; exists at all. If you need to &lt;strong&gt;read the DOM after it's updated but before the user sees it&lt;/strong&gt; — for example, measuring an element's size to position a tooltip — &lt;code&gt;useLayoutEffect&lt;/code&gt; is the only place where that's safe and accurate.&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;// useLayoutEffect — correct for DOM measurements&lt;/span&gt;
&lt;span class="nf"&gt;useLayoutEffect&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;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&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="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setTooltipPosition&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// useEffect — would cause a visible flicker for measurements&lt;/span&gt;
&lt;span class="c1"&gt;// because the browser already painted before this runs&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;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&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="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setTooltipPosition&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// too late&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use &lt;code&gt;useEffect&lt;/code&gt; for a DOM measurement that affects layout, the user will briefly see the wrong layout (before &lt;code&gt;useEffect&lt;/code&gt; runs), then see it jump to the correct layout (after). That flicker is exactly what &lt;code&gt;useLayoutEffect&lt;/code&gt; prevents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtzmw529c4p9o93d3kt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtzmw529c4p9o93d3kt7.png" alt="useLayoutEffect vs useEffect — same DOM, different timing relative to browser paint" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The "After Paint" Rule Has an Exception
&lt;/h2&gt;

&lt;p&gt;Here's something jser.dev discovered that React's own documentation gets wrong.&lt;/p&gt;

&lt;p&gt;The docs say &lt;code&gt;useEffect&lt;/code&gt; always runs after the browser paints. But that's not strictly true.&lt;/p&gt;

&lt;p&gt;Sometimes React runs &lt;code&gt;useEffect&lt;/code&gt; callbacks &lt;em&gt;before&lt;/em&gt; paint. This happens when React determines it's more important to show the latest UI as quickly as possible — for example, when a re-render is triggered by a user interaction, or when effects are scheduled under layout effects. In those cases, React won't wait for a paint before running the effects.&lt;/p&gt;

&lt;p&gt;From jser.dev's paint timing article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I'd explain the timing of running useEffect() callbacks as follows. useEffect() callbacks are run after DOM mutation is done. Most of the time, they are run asynchronously after paint, but React might run them synchronously before paint when it is more important to show the latest UI — for example when re-render is caused by user interactions or scheduled under layout effects, or simply if React has the time internally to do so."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The practical implication: never rely on &lt;code&gt;useEffect&lt;/code&gt; to run after paint for correctness. If your code requires running after the browser paints, &lt;code&gt;useLayoutEffect&lt;/code&gt; with its explicit synchronous-before-paint guarantee is actually the more predictable choice. And if you truly need to run something after paint for non-DOM reasons, the gap is usually small enough not to matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Dependency Array: What It Actually Controls
&lt;/h2&gt;

&lt;p&gt;The dependency array in &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useLayoutEffect&lt;/code&gt; doesn't control &lt;em&gt;whether&lt;/em&gt; the effect runs — it controls &lt;em&gt;when&lt;/em&gt; it re-runs.&lt;/p&gt;

&lt;p&gt;React compares the current values of the dependency array to the previous ones using &lt;code&gt;Object.is&lt;/code&gt; (similar to &lt;code&gt;===&lt;/code&gt; but handles &lt;code&gt;NaN&lt;/code&gt; and &lt;code&gt;-0&lt;/code&gt; correctly). If any value changed, React marks the effect with a flag indicating it should run again. If nothing changed, the effect is skipped this cycle.&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="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;fetchUser&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;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// only re-runs when id changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No dependency array&lt;/strong&gt; — re-runs after every render. The effect has no conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empty array &lt;code&gt;[]&lt;/code&gt;&lt;/strong&gt; — runs once on mount, cleanup runs on unmount. Effect has no dependencies that can change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Array with values&lt;/strong&gt; — re-runs whenever any listed value changes between renders.&lt;/p&gt;

&lt;p&gt;The important thing to understand: React doesn't track &lt;em&gt;what&lt;/em&gt; you use inside the effect. It trusts the dependency array you provide. If you use &lt;code&gt;userId&lt;/code&gt; inside the effect but forget to put it in the array, React won't re-run the effect when &lt;code&gt;userId&lt;/code&gt; changes — and you get a stale value bug. This is why &lt;code&gt;eslint-plugin-react-hooks&lt;/code&gt; exists: it statically analyzes your effect and warns when the array is incomplete.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Lifecycle in One Timeline
&lt;/h2&gt;

&lt;p&gt;The pattern is consistent across every scenario. On mount, the component renders, React commits the DOM, &lt;code&gt;useLayoutEffect&lt;/code&gt; fires synchronously before the browser paints, the browser paints, then &lt;code&gt;useEffect&lt;/code&gt; runs in the next macro task. On update, cleanups always run before new effects — layout cleanup first, then layout create, then paint, then passive cleanup, then passive create. On unmount, both cleanups run in the same order, layout before passive, with paint in between.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8ldjd8e0nuyeycevq2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8ldjd8e0nuyeycevq2o.png" alt="full lifecycle — mount / update / unmount with all effect timings" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things are always true regardless of the scenario: layout effects are synchronous and pre-paint, passive effects are scheduled and post-commit. And cleanups always run before the next effect of the same type — React never has two instances of the same effect active at once.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;useEffect&lt;/code&gt; for everything by default. Most side effects — data fetching, subscriptions, logging, timers — don't need to happen before the browser paints. Running them after paint keeps the UI responsive and is the right choice in the vast majority of cases.&lt;/p&gt;

&lt;p&gt;Reach for &lt;code&gt;useLayoutEffect&lt;/code&gt; only when you need to read or modify the DOM before the user sees it — measuring element dimensions, positioning a tooltip relative to another element, or preventing a visual flicker. The practical test is simple: if swapping &lt;code&gt;useLayoutEffect&lt;/code&gt; for &lt;code&gt;useEffect&lt;/code&gt; causes a visible jump or flash in the UI, you needed &lt;code&gt;useLayoutEffect&lt;/code&gt;. If it looks identical, stick with &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 6
&lt;/h2&gt;

&lt;p&gt;In Part 6 we go inside &lt;code&gt;useState&lt;/code&gt; — how state is stored on the Fiber, what happens when you call &lt;code&gt;setState&lt;/code&gt;, and why calling &lt;code&gt;setState&lt;/code&gt; multiple times in one event handler doesn't cause multiple re-renders.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=Ggmdo7TORNc&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=16" rel="noopener noreferrer"&gt;The lifecycle of effect hooks in React&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The source for the Effect object internals, &lt;code&gt;HookHasEffect&lt;/code&gt; tag, &lt;code&gt;flushPassiveEffects&lt;/code&gt;, and cleanup ordering in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=6HLvyiYv7HI&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=10" rel="noopener noreferrer"&gt;How does useLayoutEffect() work internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The synchronous commit-phase timing of layout effects vs passive effects — the source for the timing difference explained here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=2iXKUJpnALU&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=35" rel="noopener noreferrer"&gt;When do useEffect() callbacks get run? Before paint or after paint?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The surprising finding that React.dev's "always after paint" description is inaccurate — and the real timing rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — all timing mechanics in this article come from JSer's source-level analysis. Articles used: &lt;a href="https://jser.dev/react/2022/01/19/lifecycle-of-effect-hook/" rel="noopener noreferrer"&gt;The lifecycle of effect hooks in React&lt;/a&gt;, &lt;a href="https://jser.dev/react/2021/12/04/how-does-useLayoutEffect-work/" rel="noopener noreferrer"&gt;How does useLayoutEffect() work internally?&lt;/a&gt;, &lt;a href="https://jser.dev/2023-07-08-how-does-useeffect-work/" rel="noopener noreferrer"&gt;How does useEffect() work internally in React?&lt;/a&gt;, and &lt;a href="https://jser.dev/2023-08-09-effects-run-paint/" rel="noopener noreferrer"&gt;When do useEffect() callbacks get run?&lt;/a&gt; — including the quote about React.dev's inaccurate description.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React source&lt;/strong&gt; — &lt;code&gt;ReactFiberCommitWork.js&lt;/code&gt; (&lt;code&gt;commitLayoutEffects&lt;/code&gt;, &lt;code&gt;commitPassiveMountEffects&lt;/code&gt;, &lt;code&gt;commitPassiveUnmountEffects&lt;/code&gt;) and &lt;code&gt;ReactFiberWorkLoop.js&lt;/code&gt; (&lt;code&gt;scheduleCallback&lt;/code&gt; for passive effects).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for JavaScript visualizations that shaped this series' style.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 6 is next — how &lt;code&gt;useState&lt;/code&gt; actually works: where state lives, what happens when you call &lt;code&gt;setState&lt;/code&gt;, and why multiple calls in one handler don't cause multiple re-renders. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>hooks</category>
      <category>useeffect</category>
    </item>
    <item>
      <title>How React Works (Part 4)? The Idea That Makes Suspense Possible</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 12:09:09 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-4-the-idea-that-makes-suspense-possible-4gok</guid>
      <description>&lt;h2&gt;
  
  
  The Idea That Makes Suspense Possible
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd"&gt;How React Finds What Actually Changed&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1–3 first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Question Before We Start
&lt;/h2&gt;

&lt;p&gt;Think about how you fetch data in a React component today. You probably do something like this:&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;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&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="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;setUser&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;null&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;true&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;fetchUser&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;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="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="k"&gt;if &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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;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;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;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;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;It works. But look at what you had to do: manage two pieces of state, write an effect, handle the loading condition manually, repeat this pattern in every component that fetches data.&lt;/p&gt;

&lt;p&gt;Now look at what Suspense lets you write instead:&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;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&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="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="nf"&gt;fetchUser&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="c1"&gt;// just... read the data&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;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;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;div&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&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;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No loading state. No effect. No condition. The component just reads data as if it's already there. If it isn't, React handles the waiting — including showing the spinner — automatically.&lt;/p&gt;

&lt;p&gt;How is this possible? How can a component just "pause" mid-render and resume when data arrives?&lt;/p&gt;

&lt;p&gt;The answer involves a concept called &lt;strong&gt;algebraic effects&lt;/strong&gt; — and understanding it will make Suspense, ErrorBoundary, and some of React's most surprising behaviors finally make sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start With Something You Already Know: &lt;code&gt;throw&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Algebraic effects sound academic. But Dan Abramov wrote one of the best explanations of them, and he started with something every JavaScript developer already knows: &lt;code&gt;try / catch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how &lt;code&gt;throw&lt;/code&gt; works:&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;getName&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="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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no name&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="k"&gt;return&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="p"&gt;;&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="nf"&gt;getName&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="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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handled:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;Notice what &lt;code&gt;throw&lt;/code&gt; actually does. It doesn't decide what happens when the error occurs. It just &lt;em&gt;signals&lt;/em&gt; that something happened. The &lt;code&gt;catch&lt;/code&gt; block somewhere up the call stack is what decides the behavior.&lt;/p&gt;

&lt;p&gt;This separation — &lt;em&gt;signaling&lt;/em&gt; something vs &lt;em&gt;handling&lt;/em&gt; it — is the core idea.&lt;/p&gt;

&lt;p&gt;And notice: it doesn't matter how deep &lt;code&gt;getName&lt;/code&gt; is in the call stack. It could be called 100 functions deep. &lt;code&gt;throw&lt;/code&gt; bypasses all of them and finds the nearest &lt;code&gt;catch&lt;/code&gt;. The middle layers don't have to do anything. They don't even know about the error.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With &lt;code&gt;try / catch&lt;/code&gt;: No Coming Back
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;try / catch&lt;/code&gt; has one limitation that matters a lot.&lt;/p&gt;

&lt;p&gt;When you &lt;code&gt;throw&lt;/code&gt;, execution stops at that point. The &lt;code&gt;catch&lt;/code&gt; block runs. And that's it — you can never go back to the line that threw and continue from there.&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;getName&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="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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ← once you throw, you can NEVER resume here&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;user&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For errors, this makes sense. But what if you wanted to &lt;em&gt;ask&lt;/em&gt; the surrounding context a question and then &lt;em&gt;continue&lt;/em&gt; based on the answer?&lt;/p&gt;

&lt;p&gt;Like: &lt;em&gt;"I need this user's name. I don't have it. Can someone provide it? I'll wait here."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's what algebraic effects enable. Dan Abramov described it as a "resumable &lt;code&gt;try / catch&lt;/code&gt;."&lt;/p&gt;




&lt;h2&gt;
  
  
  Algebraic Effects: A Resumable &lt;code&gt;try / catch&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Algebraic effects don't exist in JavaScript yet. They're a research concept — supported only by a handful of languages built specifically to explore the idea. But Dan Abramov explained them using a hypothetical JavaScript dialect, and his explanation is the clearest one I've found. Let's walk through it.&lt;/p&gt;

&lt;p&gt;Imagine JavaScript had two new keywords: &lt;code&gt;perform&lt;/code&gt; and &lt;code&gt;resume with&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;perform&lt;/code&gt; works like &lt;code&gt;throw&lt;/code&gt; — it signals something to the surrounding context and finds the nearest handler up the call stack. The crucial difference: it doesn't stop execution. The handler can call &lt;code&gt;resume with&lt;/code&gt; to jump &lt;em&gt;back&lt;/em&gt; to exactly where &lt;code&gt;perform&lt;/code&gt; was called and continue from there, passing a value back in.&lt;/p&gt;

&lt;p&gt;Here's Dan's example. We have a function that needs a user's name but doesn't have it:&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;// Hypothetical JavaScript — this doesn't exist yet&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getName&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="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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Signal: "I need a name" — but don't stop&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;perform&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ask_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// 4. Resume here — name is now 'Arya Stark'&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;user&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;makeFriends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arya&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gendry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nf"&gt;handle &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;)&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;effect&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ask_name&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="c1"&gt;// 2. Handler catches it — like catch&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. Resume with a value — unlike catch&lt;/span&gt;
    &lt;span class="nx"&gt;resume&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Arya Stark&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;The numbered steps show what happens: &lt;code&gt;perform&lt;/code&gt; signals (1), the handler catches it (2), the handler provides a value (3), execution jumps back to where &lt;code&gt;perform&lt;/code&gt; was and continues with that value (4).&lt;/p&gt;

&lt;p&gt;Notice what this doesn't require. &lt;code&gt;makeFriends&lt;/code&gt; — the function between &lt;code&gt;getName&lt;/code&gt; and the handler — doesn't know anything about this. It didn't have to be modified. It didn't have to pass anything through. The effect just bypassed it entirely, exactly like &lt;code&gt;throw&lt;/code&gt; bypasses intermediate functions on its way to &lt;code&gt;catch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is what Dan calls &lt;strong&gt;"a function has no color."&lt;/strong&gt; With &lt;code&gt;async/await&lt;/code&gt;, making one function async infects every function above it — they all have to become &lt;code&gt;async&lt;/code&gt; too. With algebraic effects, a deeply nested function can perform an effect without any of the layers above it caring. The handler somewhere at the top decides what to do, and execution resumes as if nothing unusual happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes this more powerful than &lt;code&gt;try / catch&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;With regular &lt;code&gt;try / catch&lt;/code&gt;, the handler is always &lt;em&gt;synchronous&lt;/em&gt; and can never return a value back to the thrower. With algebraic effects, the handler can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Respond synchronously — &lt;code&gt;resume with&lt;/code&gt; an immediate value&lt;/li&gt;
&lt;li&gt;Respond asynchronously — call &lt;code&gt;resume with&lt;/code&gt; inside a &lt;code&gt;setTimeout&lt;/code&gt; or after a fetch&lt;/li&gt;
&lt;li&gt;The code that did &lt;code&gt;perform&lt;/code&gt; doesn't need to know which one happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last point is the key. The same &lt;code&gt;getName&lt;/code&gt; function works whether the handler looks up the name from memory, fetches it from a database, or generates it randomly. The function that &lt;em&gt;needs&lt;/em&gt; the effect is completely decoupled from how the effect is &lt;em&gt;fulfilled&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7btgkkd8c9mmznuamta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7btgkkd8c9mmznuamta.png" alt="bypasses middle layers → handler → resume with value → continues at perform point" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That decoupling — signal here, handle somewhere above, resume back here — is the entire concept. And it maps almost exactly onto what React does with Suspense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Suspense Is React's Implementation of This Idea
&lt;/h2&gt;

&lt;p&gt;JavaScript doesn't actually have &lt;code&gt;perform&lt;/code&gt; and &lt;code&gt;resume with&lt;/code&gt;. They don't exist. But React simulates this pattern using the tools JavaScript does have: &lt;code&gt;throw&lt;/code&gt; and Fiber.&lt;/p&gt;

&lt;p&gt;Here's what actually happens when a component suspends:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The component throws a Promise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;fetchUser(id)&lt;/code&gt; is called and the data isn't in the cache yet, instead of returning &lt;code&gt;null&lt;/code&gt; or undefined, it throws a Promise — the Promise that will resolve when the data arrives.&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;// Inside the data fetching library&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← this is the "perform"&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;cached&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resolved&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="c1"&gt;// ← data is ready, return normally&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Start the fetch, mark as pending, throw&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&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;`/users/&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;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;promise&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;React catches the thrown Promise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because React's render loop runs inside a &lt;code&gt;try / catch&lt;/code&gt;, it catches the thrown Promise. This is the "handler" in the algebraic effects model — React itself acts as the effect handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React shows the fallback.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;React walks up the Fiber tree to find the nearest &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundary and shows its fallback — the &lt;code&gt;&amp;lt;Spinner /&amp;gt;&lt;/code&gt; — while waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the Promise resolves, React retries.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the data arrives, React re-renders the component from the &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundary downward. This time, the cache has the data. &lt;code&gt;fetchUser&lt;/code&gt; returns normally instead of throwing. The component renders successfully.&lt;/p&gt;

&lt;p&gt;As Sam Galson described it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A component is able to suspend the fiber it is running in by throwing a promise, which is caught and handled by the framework. When the promise resolves, its value is added to a cache, the fiber is restarted, and the next time the data is requested it is accessed synchronously from cache."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyas3cr145oa4z3vtia0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyas3cr145oa4z3vtia0.png" alt="component throws Promise → Suspense boundary catches → shows fallback → Promise resolves → retry → renders" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Only Works Because of Fiber
&lt;/h2&gt;

&lt;p&gt;Here's the critical question: when a component throws mid-render, wouldn't all the work React did to reach that point be lost?&lt;/p&gt;

&lt;p&gt;With the old synchronous call stack — yes. Throwing would unwind the entire stack, destroying every local variable and stack frame on the way up.&lt;/p&gt;

&lt;p&gt;But React uses Fiber, not the native call stack, to track rendering work. Fibers are plain JavaScript objects — they survive the &lt;code&gt;throw&lt;/code&gt;. They sit in memory exactly as they were.&lt;/p&gt;

&lt;p&gt;When React catches the thrown Promise and shows the fallback, all the Fiber work up to that point is preserved. When the data arrives and React retries, it resumes from the Suspense boundary without re-doing all the ancestor work.&lt;/p&gt;

&lt;p&gt;This is what Sam Galson meant when he wrote that the throw-based approach "isn't problematic for React because it relies on fibers not the native call stack to track program execution."&lt;/p&gt;

&lt;p&gt;Fiber made algebraic-effect-style control flow possible in JavaScript. Without Fiber, throwing a Promise would be catastrophic. With Fiber, it's just a pause.&lt;/p&gt;




&lt;h2&gt;
  
  
  ErrorBoundary Is the Same Pattern
&lt;/h2&gt;

&lt;p&gt;Once you understand Suspense through this lens, ErrorBoundary becomes obvious — it's the same mechanism, just for actual errors instead of Promises.&lt;/p&gt;

&lt;p&gt;When a component throws an error during rendering, React walks up the Fiber tree looking for the nearest class component that implements &lt;code&gt;getDerivedStateFromError&lt;/code&gt;. When it finds one, it re-renders that component with error state, showing the fallback UI instead of the crashed subtree.&lt;/p&gt;

&lt;p&gt;The component that threw didn't decide what happens — it just threw. The ErrorBoundary somewhere up the tree decided the behavior. Sound familiar?&lt;/p&gt;

&lt;p&gt;That's the same separation as &lt;code&gt;perform&lt;/code&gt; and &lt;code&gt;handle&lt;/code&gt;. The throwing component signals something happened. The handler decides what to do.&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;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getDerivedStateFromError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="na"&gt;hasError&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="c1"&gt;// ← this is the "handle"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&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="k"&gt;this&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;hasError&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Something went wrong.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&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;The key difference from Suspense: ErrorBoundary doesn't resume. Once a component threw an error, that render is abandoned. The boundary re-renders with fallback. Suspense, by contrast, genuinely retries the original render when the data arrives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk3qgqv09j1a9vk7mliv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk3qgqv09j1a9vk7mliv.png" alt="ErrorBoundary vs Suspense — both catch throws, ErrorBoundary shows fallback permanently, Suspense retries" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Dan Abramov wrote in his algebraic effects article that the React team — specifically Sebastian Markbåge — had been using algebraic effects as a mental model for years:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"My colleague Sebastian kept referring to them as a mental model for some things we do inside of React. At some point, it became a running joke on the React team."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hooks follow the same idea too. When you call &lt;code&gt;useState&lt;/code&gt; inside a component, that component doesn't know or care where its state is actually stored. It just performs a "request for state" and React — the handler — provides it from the Fiber's hook linked list. The component doesn't reach into React's internals. React provides the value through the call.&lt;/p&gt;

&lt;p&gt;This separation — components declaring &lt;em&gt;what they need&lt;/em&gt;, React deciding &lt;em&gt;how to provide it&lt;/em&gt; — is algebraic effects thinking applied to a UI library. And it's why React's API feels declarative even when the underlying behavior is complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 5
&lt;/h2&gt;

&lt;p&gt;In Part 5 we look at the React lifecycle from the inside — initial mount, re-render, &lt;code&gt;useEffect&lt;/code&gt;, and &lt;code&gt;useLayoutEffect&lt;/code&gt;. We'll trace exactly when each fires, why the order is what it is, and what "after the browser paints" actually means.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sam Galson — &lt;a href="https://www.youtube.com/watch?v=5OPyjrKytLU" rel="noopener noreferrer"&gt;Magic in the web of it: coroutines, continuations, fibers | React Advanced London&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The CS foundation — fibers, coroutines, and how React uses the throw/catch mechanism to simulate algebraic effects. The source for the Fiber + algebraic effects connection in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matheus Albuquerque — &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4&amp;amp;t=766s" rel="noopener noreferrer"&gt;Inside Fiber | React Summit 2022&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Start at &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4&amp;amp;t=766s" rel="noopener noreferrer"&gt;12:46&lt;/a&gt; — Matheus shows how Context API was designed using algebraic effects thinking, and how Suspense connects to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dan Abramov&lt;/strong&gt; — &lt;a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/" rel="noopener noreferrer"&gt;Algebraic Effects for the Rest of Us&lt;/a&gt; on overreacted.io. The full three-step build in this article — &lt;code&gt;try/catch&lt;/code&gt; → limitation → &lt;code&gt;perform/resume with&lt;/code&gt; — follows Dan's progression directly. The Arya Stark example, the "resumable try/catch" framing, the "function has no color" insight about async infection, the async handler example, and the quote about Sebastian Markbåge using algebraic effects as a running joke on the React team — all come verbatim or paraphrased from this article. Read it after this one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sam Galson&lt;/strong&gt; — &lt;a href="https://www.youtube.com/watch?v=5OPyjrKytLU" rel="noopener noreferrer"&gt;Magic in the web of it (talk)&lt;/a&gt; and &lt;a href="https://www.yld.com/blog/continuations-coroutines-fibers-effects" rel="noopener noreferrer"&gt;Continuations, coroutines, fibers, effects (article)&lt;/a&gt;. The quote about how "a component is able to suspend the fiber it is running in by throwing a promise" and the throw-handle-resume pattern description come directly from Sam's article.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sebastian Markbåge&lt;/strong&gt; — for the original "Poor man's algebraic effects" gist that modeled the technique later used by React Suspense, cited in Sam Galson's article.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — source verification of how &lt;code&gt;throwException&lt;/code&gt;, the Suspense retry mechanism, and ErrorBoundary recovery actually work in the React codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 5 is next — the React lifecycle from the inside: when &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useLayoutEffect&lt;/code&gt; fire, and why the order is exactly what it is. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>suspense</category>
      <category>algebraiceffects</category>
    </item>
    <item>
      <title>How React Works (Part 3)? How React Finds What Actually Changed</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 11:32:44 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-3-how-react-finds-what-actually-changed-17nd</guid>
      <description>&lt;h2&gt;
  
  
  How React Finds What Actually Changed
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k"&gt;Why React Had to Build Its Own Execution Engine&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Parts 1 and 2 first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where We Left Off
&lt;/h2&gt;

&lt;p&gt;In Part 2 we understood the engine — Fiber gives React a custom call stack it controls, and the Scheduler decides what to run and when.&lt;/p&gt;

&lt;p&gt;Now the question is: what does React actually &lt;em&gt;do&lt;/em&gt; with that engine?&lt;/p&gt;

&lt;p&gt;Every time state changes, React has a problem. It knows the &lt;em&gt;old&lt;/em&gt; version of your UI and the &lt;em&gt;new&lt;/em&gt; version. But it's working with a tree of potentially thousands of components. Re-creating or re-rendering all of them on every update would be catastrophically slow.&lt;/p&gt;

&lt;p&gt;React needs to answer one question as efficiently as possible:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Of everything that could have changed, what actually did?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The algorithm that answers that question is called the &lt;strong&gt;Reconciler&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Re-rendering Everything Is Too Slow
&lt;/h2&gt;

&lt;p&gt;Let's make the problem concrete. You have an app with 500 components. A user clicks a button that changes one counter inside one component. Naively, React would need to re-run every component function, create new JSX, and compare it to the old DOM — 500 times — to figure out that exactly one &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; needs its text updated.&lt;/p&gt;

&lt;p&gt;That's what old-school frameworks did. It's why they felt slow.&lt;/p&gt;

&lt;p&gt;React's insight was: &lt;strong&gt;most of the tree didn't change.&lt;/strong&gt; If React can quickly identify which parts of the tree could possibly have changed, it can skip everything else and only do real work on the parts that matter.&lt;/p&gt;

&lt;p&gt;The Reconciler is that skip logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First Skip: Following the Trail
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;setState&lt;/code&gt; inside a component, React doesn't just mark that one component for update. It marks a trail — like breadcrumbs from that component all the way back to the root of the tree.&lt;/p&gt;

&lt;p&gt;Every ancestor gets a small flag saying: &lt;em&gt;"one of your descendants has work to do."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk45wipykxal0f3dh1yuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk45wipykxal0f3dh1yuk.png" alt="etState on a deep component — trail of flags bubbles up to root" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then when React walks the tree to find what needs re-rendering, it uses those flags like a GPS. At every node it asks: &lt;em&gt;"Does this node have work? No? Do any of its children have work? Also no?"&lt;/em&gt; If both answers are no, React skips that entire subtree in one step — not just the node, but every component inside it.&lt;/p&gt;

&lt;p&gt;This is how a click inside one deeply nested component avoids touching the 490 components that had nothing to do with it. React walks straight down the trail of flags, skips every branch that's clean, and only stops at the one component that actually changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Second Skip: Props Haven't Changed
&lt;/h2&gt;

&lt;p&gt;React made it to the component that has work. But before actually re-running it, React checks one more thing: did this component's &lt;em&gt;inputs&lt;/em&gt; (props) change?&lt;/p&gt;

&lt;p&gt;If a parent re-renders, it might generate new props for its children. But "new" doesn't always mean "different." React compares the old props to the new props. If they're the same object reference — same pointer in memory — React skips re-rendering that component entirely.&lt;/p&gt;

&lt;p&gt;This is reference equality, not deep equality. React doesn't compare every field inside the props object. It just checks: &lt;em&gt;is this the exact same object we had before?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If yes — skip. The component can't possibly look different, so there's nothing to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is also exactly how &lt;code&gt;React.memo&lt;/code&gt; works.&lt;/strong&gt; &lt;code&gt;React.memo&lt;/code&gt; wraps a component and adds one step: when React would normally re-render because the props reference changed, &lt;code&gt;React.memo&lt;/code&gt; does a shallow comparison of each prop value. If they all match, it skips. If even one is different, it re-renders normally.&lt;/p&gt;

&lt;p&gt;And this is why passing a new object as a prop breaks memoization:&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;// This breaks React.memo — new object created every render&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;style&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="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// This works — same reference if color hasn't changed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{}&lt;/code&gt; on the first line creates a brand-new object every single render. Even though its contents are identical, it's a different object in memory. React sees "different reference" and re-renders.&lt;/p&gt;




&lt;h2&gt;
  
  
  When React Does Need to Re-render: Type vs Content
&lt;/h2&gt;

&lt;p&gt;React made it to a component that needs updating. It runs the component function, gets new JSX back, and now needs to figure out what changed by comparing old output to new output.&lt;/p&gt;

&lt;p&gt;The first thing React checks is the &lt;strong&gt;type&lt;/strong&gt; of each element. Not the content — the type.&lt;/p&gt;

&lt;p&gt;If you had a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and now you have a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;, React doesn't try to figure out how to transform one into the other. It destroys the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; completely — the DOM node, the fiber, all state inside it — and creates a fresh &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; from scratch.&lt;/p&gt;

&lt;p&gt;This is a hard rule: &lt;strong&gt;same type means update in place, different type means destroy and recreate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuj7lcmdcco9ht5qq0o1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuj7lcmdcco9ht5qq0o1p.png" alt="same type — update, different type — destroy and create fresh" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This rule has a real consequence that surprises developers:&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;// Every time isLoggedIn changes, the entire form loses its state&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserForm&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GuestForm&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// UserForm and GuestForm are different types — React destroys one and creates the other&lt;/span&gt;
&lt;span class="c1"&gt;// Any input values typed into the form are gone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to keep state when switching between two components, they need to be the same type, rendered at the same position in the tree. Different types always means starting over.&lt;/p&gt;




&lt;h2&gt;
  
  
  The List Problem: Why &lt;code&gt;key&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;Single elements are straightforward to diff. Lists are where it gets genuinely hard.&lt;/p&gt;

&lt;p&gt;Imagine you have a list of 5 items. Now you add a new item at the top. From React's perspective, looking position by position: position 1 changed, position 2 changed, position 3 changed, position 4 changed, position 5 changed, position 6 is new. It looks like every single item changed — even though you just prepended one.&lt;/p&gt;

&lt;p&gt;Without any extra information, React would re-render all 5 existing items unnecessarily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltxxxg4tf3t087ljuvyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltxxxg4tf3t087ljuvyn.png" alt="list without keys — prepend causes all items to look different by position" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what &lt;code&gt;key&lt;/code&gt; solves. A &lt;code&gt;key&lt;/code&gt; is a stable identifier that tells React: &lt;em&gt;"this item is this item, regardless of where it appears in the list."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With keys, React builds a lookup table of all existing items keyed by their identifier. Then for each new item in the list, it looks up: &lt;em&gt;"do I already have a fiber for this key?"&lt;/em&gt; If yes, reuse it. If no, create it fresh. Items that were in the old list but aren't in the new one get deleted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyptk9k8y5ai99lowdvwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyptk9k8y5ai99lowdvwv.png" alt="list with keys — React looks up by key, only new item is created" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The practical result: prepend one item to a list of 1000, and React creates exactly one new fiber. The other 999 are reused instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why index as key breaks things:&lt;/strong&gt;&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;// Dangerous — index as key&lt;/span&gt;
&lt;span class="nx"&gt;items&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;item&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Row&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;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you prepend a new item, every index shifts. The new item gets &lt;code&gt;key=0&lt;/code&gt;, which used to belong to the old first item. React thinks it's reusing the old component — but it's actually showing different data through the same fiber. State from the old item leaks into the new item. Form inputs show wrong values. This is a genuinely hard bug to track down.&lt;/p&gt;

&lt;p&gt;The fix is always the same: use something stable and unique as the key — an ID from your data, a slug, anything that doesn't change when the list is reordered.&lt;/p&gt;




&lt;h2&gt;
  
  
  After Finding the Changes: Marking, Not Doing
&lt;/h2&gt;

&lt;p&gt;Here's a subtle but important detail about how React works.&lt;/p&gt;

&lt;p&gt;During the Reconciler's walk of the tree, React does &lt;strong&gt;not&lt;/strong&gt; touch the real DOM. It doesn't insert nodes, update text, or remove elements. It only &lt;em&gt;marks&lt;/em&gt; fibers with notes about what needs to happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This fiber needs a new DOM node inserted&lt;/li&gt;
&lt;li&gt;This fiber's props changed and the DOM needs updating
&lt;/li&gt;
&lt;li&gt;This fiber was removed and its DOM node should be deleted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like a doctor marking a patient's chart before surgery — deciding what needs to happen, not doing it yet.&lt;/p&gt;

&lt;p&gt;Only after the entire tree has been walked and all decisions are made does React move to the Commit phase — where it reads those marks and makes the actual DOM changes, all at once, in a single synchronous pass.&lt;/p&gt;

&lt;p&gt;This separation is what makes React's updates feel atomic. You never see a half-updated UI. Either the whole update is done, or none of it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Picture in One Paragraph
&lt;/h2&gt;

&lt;p&gt;Every state change triggers a tree walk. React follows a trail of flags to find what might have changed, skips every clean subtree instantly, checks whether props actually changed before re-running anything, compares old and new output by type first and content second, uses &lt;code&gt;key&lt;/code&gt; props to track list items regardless of position, and marks everything that needs updating — without touching the DOM. Then, in one final pass, it applies all the marks at once.&lt;/p&gt;

&lt;p&gt;That's reconciliation. It's why updating one input in a form with 200 fields is fast, why &lt;code&gt;React.memo&lt;/code&gt; works, and why &lt;code&gt;key&lt;/code&gt; is not optional for lists.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 4
&lt;/h2&gt;

&lt;p&gt;In Part 4 we look at one of the most interesting ideas React borrowed from academic computer science to make Suspense and ErrorBoundary work — algebraic effects. It's simpler than it sounds, and understanding it will make how Suspense &lt;em&gt;actually&lt;/em&gt; works finally click.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=0GM-1W7i9Tk&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=3" rel="noopener noreferrer"&gt;How does React re-render internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Source-level walkthrough of the re-render process — the trail of flags, props comparison, and the two-phase render/commit split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=LfwMlGjiaW0&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=13" rel="noopener noreferrer"&gt;How does React bailout work?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Why most of your component tree gets skipped on every render, and what exactly triggers a re-render vs a skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSer (jser.dev) — &lt;a href="https://www.youtube.com/watch?v=hDflM4IGCN8&amp;amp;list=PLvx8w9g4qv_p-OS-XdbB3Ux_6DMXhAJC3&amp;amp;index=19" rel="noopener noreferrer"&gt;How does 'key' work internally?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The full list diffing algorithm — how React uses keys to match old and new items, and why the wrong key causes bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — the bailout mechanics (trail of flags, props comparison), re-render algorithm, and key/list diffing all come from JSer's source-level analysis. Articles: &lt;a href="https://jser.dev/2023-07-18-how-react-rerenders/" rel="noopener noreferrer"&gt;How does React re-render internally?&lt;/a&gt;, &lt;a href="https://jser.dev/react/2022/01/07/how-does-bailout-work/" rel="noopener noreferrer"&gt;How does React bailout work?&lt;/a&gt;, &lt;a href="https://jser.dev/react/2022/02/08/the-diffing-algorithm-for-array-in-react/" rel="noopener noreferrer"&gt;How does 'key' work internally?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Andrew Clark&lt;/strong&gt; — &lt;a href="https://github.com/acdlite/react-fiber-architecture" rel="noopener noreferrer"&gt;react-fiber-architecture&lt;/a&gt; — the reconciler/renderer separation that explains why React marks before it commits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for JavaScript visualizations that shaped this series' style.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 4 is next — algebraic effects, and how Suspense actually works under the hood. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>reconciler</category>
      <category>memo</category>
    </item>
    <item>
      <title>How React Works (Part 2)? Why React Had to Build Its Own Execution Engine</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 10:44:11 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-2-why-react-had-to-build-its-own-execution-engine-119k</guid>
      <description>&lt;h2&gt;
  
  
  Why React Had to Build Its Own Execution Engine
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4"&gt;Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Read Part 1 first — this article picks up exactly where it left off.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where We Left Off
&lt;/h2&gt;

&lt;p&gt;In Part 1 we landed on one sentence that explains why React had to be completely rewritten:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;React needs to be able to interrupt what it's doing and resume it later.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Time Slicing needs it — to yield within the browser's 16ms frame budget.&lt;br&gt;
Suspense needs it — to pause and wait for async data.&lt;/p&gt;

&lt;p&gt;Simple enough requirement. But here's the problem: &lt;strong&gt;JavaScript itself won't let you do it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article is about why that's true, what React did about it, and why that decision — building an entirely custom execution model — is what makes modern React possible.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem: JavaScript Runs to Completion
&lt;/h2&gt;

&lt;p&gt;When JavaScript starts executing a function, it finishes it. There is no external API to say "pause here, do something else, come back." There is no way to freeze a running program mid-execution, hand control back to the browser, and resume from the exact same spot.&lt;/p&gt;

&lt;p&gt;This is because JavaScript manages execution with something called the &lt;strong&gt;call stack&lt;/strong&gt; — a data structure that tracks what the program is doing at any moment.&lt;/p&gt;

&lt;p&gt;Every time a function is called, JavaScript creates a &lt;strong&gt;stack frame&lt;/strong&gt; — a small record of that function's parameters, local variables, and where to return to when it finishes. These frames stack up as functions call other functions, and pop off as they return.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F065mkfqin93gfw6bogr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F065mkfqin93gfw6bogr6.png" alt="call stack growing as functions call each other, frames popping as they return" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the critical property of this stack: &lt;strong&gt;it's managed entirely by the JavaScript engine.&lt;/strong&gt; You — the developer — cannot read it, pause it, or jump around in it. It's a black box. Once a chain of function calls starts, it runs to completion. The browser can't paint. The user can't interact. Nothing happens until the stack is empty.&lt;/p&gt;

&lt;p&gt;For old React, this was fine. React rendered your whole tree in one big chain of function calls. The browser waited. Nobody noticed because apps were small enough.&lt;/p&gt;

&lt;p&gt;But for Time Slicing — where React needs to stop mid-render, let the browser paint, then resume — this model is a hard wall. You can see the exact moment React solved it in two lines from the React source:&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;// Old — no way out until every component is done&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;workLoopSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;performUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&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="c1"&gt;// New — checks after every single component&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;workLoopConcurrent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;shouldYield&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;performUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&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;One extra condition: &lt;code&gt;!shouldYield()&lt;/code&gt;. That's it. After every component, React asks: &lt;em&gt;"Is the browser waiting? Is there higher-priority work?"&lt;/em&gt; If yes, the loop breaks. But for this to be safe — for breaking mid-loop to not lose work — React needed somewhere to store what it was doing. That storage is Fiber.&lt;/p&gt;

&lt;p&gt;Sam Galson described it precisely in his talk "Magic in the web of it":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Unlike stack frames, fibers can also keep around local data like state even if they're not currently active. With a stack frame, when it's popped off the stack, all of the local information stored in it is gone. But that's not the case with a fiber."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Insight: What If React Had Its Own Stack?
&lt;/h2&gt;

&lt;p&gt;Here's the idea that changed everything.&lt;/p&gt;

&lt;p&gt;The native JavaScript call stack is a constraint because React doesn't control it. But what if React built its own version — one that it &lt;em&gt;does&lt;/em&gt; control?&lt;/p&gt;

&lt;p&gt;Not a replacement for JavaScript's stack. JavaScript still runs normally. But React would track its own rendering work in a data structure it manages itself. One it can pause, inspect, prioritize, and resume at any moment.&lt;/p&gt;

&lt;p&gt;This is what React Fiber is. It is React's own custom execution model, built in plain JavaScript objects, sitting on top of JavaScript's normal execution.&lt;/p&gt;

&lt;p&gt;Sam Galson described the CS concept behind it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A fiber is just a generic way to model program execution in a way where each individual unit of work works together cooperatively. No single fiber attempts to dominate the program."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React didn't invent this idea. Fibers exist in operating systems. Microsoft Windows uses them. OCaml built its entire concurrency model on them. The concept is old — React just brought it to the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Fiber Actually Is
&lt;/h2&gt;

&lt;p&gt;In JavaScript's call stack, a &lt;strong&gt;stack frame&lt;/strong&gt; holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What function is running&lt;/li&gt;
&lt;li&gt;What parameters it was called with&lt;/li&gt;
&lt;li&gt;What local variables it has&lt;/li&gt;
&lt;li&gt;Where to return to when done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;Fiber&lt;/strong&gt; holds the exact same information — but for a React component:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack frame&lt;/th&gt;
&lt;th&gt;Fiber equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The function&lt;/td&gt;
&lt;td&gt;Your component (&lt;code&gt;App&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameters&lt;/td&gt;
&lt;td&gt;Props being passed in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local variables&lt;/td&gt;
&lt;td&gt;State and hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return address&lt;/td&gt;
&lt;td&gt;The parent component&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference that makes everything possible: stack frames live inside the JavaScript engine where you can't touch them. &lt;strong&gt;Fibers are just plain JavaScript objects.&lt;/strong&gt; React creates them, reads them, pauses on them, and resumes them — completely under its own control.&lt;/p&gt;

&lt;p&gt;Here's what a fiber for a &lt;code&gt;&amp;lt;Button&amp;gt;&lt;/code&gt; component actually looks like at runtime:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// your component function&lt;/span&gt;
  &lt;span class="nx"&gt;pendingProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// props coming in&lt;/span&gt;
  &lt;span class="nx"&gt;memoizedState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;count&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="c1"&gt;// state lives here&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ParentFiber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// parent — like a return address&lt;/span&gt;
  &lt;span class="nx"&gt;child&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;span class="c1"&gt;// first child component&lt;/span&gt;
  &lt;span class="nx"&gt;sibling&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;span class="c1"&gt;// next sibling component&lt;/span&gt;
  &lt;span class="nx"&gt;alternate&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;span class="c1"&gt;// the other version of this fiber&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A regular JavaScript object. Nothing magical. The fact that it's &lt;em&gt;just an object&lt;/em&gt; is what makes pausing safe — React can stop at any point, and the fiber is still sitting in memory exactly as it was.&lt;/p&gt;

&lt;p&gt;And because fibers are objects, they can hold more than stack frames can. Each fiber knows not just its parent (like a return address) but also its &lt;strong&gt;first child&lt;/strong&gt; and its &lt;strong&gt;next sibling&lt;/strong&gt;. This means React can navigate the entire component tree from any point — forward, backward, sideways — without losing track of where it is.&lt;/p&gt;

&lt;p&gt;When the browser needs to paint, React stops updating &lt;code&gt;workInProgress&lt;/code&gt; (its pointer to the current fiber), yields control, and returns to the work loop later. The fiber is still sitting in memory, exactly as React left it. &lt;strong&gt;Nothing was lost.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Trees, Always
&lt;/h2&gt;

&lt;p&gt;Here's one more piece of the picture that makes this safe.&lt;/p&gt;

&lt;p&gt;React always keeps &lt;strong&gt;two versions&lt;/strong&gt; of your component tree in memory at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;current tree&lt;/strong&gt; — what's actually showing on screen right now&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;work-in-progress tree&lt;/strong&gt; — what React is building for the next render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All rendering work happens on the work-in-progress tree. The current tree is never touched during rendering. So if React gets interrupted mid-render, the screen doesn't flicker or break — the current tree is still intact and showing correctly.&lt;/p&gt;

&lt;p&gt;When the new tree is fully built and ready, React swaps them in one atomic operation. What was work-in-progress becomes current. What was current becomes the next work-in-progress (ready to be reused for the render after that).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73y7p4q0p8h57zka7nvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73y7p4q0p8h57zka7nvv.png" alt="two trees — current (on screen) and work-in-progress (being built), swap on commit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pattern — keeping two versions and building the new one safely before switching — is called &lt;strong&gt;double buffering&lt;/strong&gt;. It's the same technique video games use to prevent screen tearing. React borrowed it for exactly the same reason: you never want to show the user a half-built state.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Scheduler Uses This
&lt;/h2&gt;

&lt;p&gt;Now the Scheduler — the module that decides &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;in what order&lt;/em&gt; work runs — can do things that were completely impossible before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pause and resume.&lt;/strong&gt; When &lt;code&gt;shouldYield()&lt;/code&gt; returns true (time budget used up), the work loop simply stops. The &lt;code&gt;workInProgress&lt;/code&gt; pointer stays exactly where it is. Next time the Scheduler gets control, the loop continues from that fiber. No work is lost. No state is rebuilt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prioritize.&lt;/strong&gt; Because fibers are objects React controls, it can look at any fiber's priority (&lt;code&gt;lanes&lt;/code&gt;) and decide to work on a different part of the tree first. A keystroke comes in while React is rendering a heavy chart? React stops the chart render, handles the keystroke (which runs at higher priority), then comes back to the chart from exactly where it left off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abort.&lt;/strong&gt; If a newer update makes the current work-in-progress tree obsolete, React can throw it away entirely and start fresh with updated data. The current tree on screen is unaffected.&lt;/p&gt;

&lt;p&gt;None of these were possible when rendering was one continuous chain of JavaScript function calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnji56dwnp0vhn826op04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnji56dwnp0vhn826op04.png" alt="Scheduler work loop — shouldYield check, pause, high-priority task runs, resume" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Picture in One Paragraph
&lt;/h2&gt;

&lt;p&gt;React saw that JavaScript's call stack — fast, simple, but completely opaque — was incompatible with the kind of interruptible, prioritized rendering that modern apps need. So it built a replacement: a tree of plain JavaScript objects (fibers) where each object is a stack frame React controls. It keeps two copies of this tree so it can always build the next version safely without disturbing what's on screen. And it has a Scheduler that decides what to work on next, how long to work before yielding, and how to resume exactly where it stopped.&lt;/p&gt;

&lt;p&gt;That system — Fiber + Scheduler — is the engine underneath every &lt;code&gt;useState&lt;/code&gt;, every &lt;code&gt;useTransition&lt;/code&gt;, every &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundary, every animation that doesn't drop frames.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in Part 3
&lt;/h2&gt;

&lt;p&gt;Now that we understand the engine, we can look at what the engine actually &lt;em&gt;does&lt;/em&gt; — how React figures out which parts of your UI actually changed, and how it updates the minimum number of DOM nodes to reflect that. That's the Reconciler, and it's where &lt;code&gt;key&lt;/code&gt; props, &lt;code&gt;React.memo&lt;/code&gt;, and most of your performance intuitions come from.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 Watch These
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sam Galson — &lt;a href="https://www.youtube.com/watch?v=5OPyjrKytLU" rel="noopener noreferrer"&gt;Magic in the web of it: coroutines, continuations, fibers | React Advanced London&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The computer science behind why Fiber works — what fibers are in CS terms, how they differ from threads and coroutines, and why React chose this model. The source for the cooperative execution framing in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dan Abramov — &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4" rel="noopener noreferrer"&gt;Beyond React 16 | JSConf Iceland 2018&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The original demo of the problem this article solves. Watch &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=177s" rel="noopener noreferrer"&gt;2:57&lt;/a&gt; to feel exactly why the call stack was a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matheus Albuquerque — &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4" rel="noopener noreferrer"&gt;Inside Fiber | React Summit 2022&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Goes deeper on the FiberNode data structure if you want the implementation detail. Start at &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4&amp;amp;t=131s" rel="noopener noreferrer"&gt;2:11&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sam Galson&lt;/strong&gt; — &lt;a href="https://www.youtube.com/watch?v=5OPyjrKytLU" rel="noopener noreferrer"&gt;Magic in the web of it (talk)&lt;/a&gt; and &lt;a href="https://medium.com/yld-blog/continuations-coroutines-fibers-effects-e163dda9dedc" rel="noopener noreferrer"&gt;Continuations, coroutines, fibers, effects (article)&lt;/a&gt;. The cooperative execution framing, the "fibers existed before React" point, and the comparison between fibers and stack frames all come from Sam's work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Andrew Clark&lt;/strong&gt; — &lt;a href="https://github.com/acdlite/react-fiber-architecture" rel="noopener noreferrer"&gt;react-fiber-architecture&lt;/a&gt;. The definition of Fiber as "a reimplementation of the stack, specialized for React components" and the double buffering concept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — source-level verification of how the two-tree system, &lt;code&gt;workInProgress&lt;/code&gt;, and the Scheduler work loop actually behave in the React codebase.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 3 is next — the Reconciler: how React finds the minimum set of changes needed to update your UI, and why &lt;code&gt;key&lt;/code&gt; props matter more than you think. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>scheduler</category>
      <category>fiber</category>
    </item>
    <item>
      <title>How React Works (Part 1)?Motivation Behind React Fiber: Time Slicing &amp; Suspense</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Sat, 02 May 2026 09:58:18 +0000</pubDate>
      <link>https://forem.com/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4</link>
      <guid>https://forem.com/samabaasi/how-react-works-part-1motivation-behind-react-fiber-time-slicing-suspense-4gf4</guid>
      <description>&lt;h2&gt;
  
  
  Motivation Behind React Fiber: Time Slicing &amp;amp; Suspense
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; How React Works Under the Hood&lt;br&gt;
&lt;strong&gt;Level:&lt;/strong&gt; Intermediate to Advanced&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Basic React knowledge. Bonus: if you've read &lt;a href="https://dev.to/samabaasi/how-javascript-works-part-1-look-at-js-engine-1kkb"&gt;How JavaScript Works&lt;/a&gt; — you'll feel right at home.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You've used React. You've felt that lag — the input that stutters, the page that freezes for half a second, the animation that just doesn't feel right. But have you ever wondered &lt;strong&gt;why React had to completely rewrite its entire internal engine just to fix it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In 2017, the React team announced &lt;strong&gt;React Fiber&lt;/strong&gt; — a ground-up rewrite of React's internals. Not the API. Your JSX and components stayed the same. But the engine underneath, the thing that actually decides &lt;em&gt;how&lt;/em&gt; and &lt;em&gt;when&lt;/em&gt; work gets done, was thrown out and rebuilt from scratch.&lt;/p&gt;

&lt;p&gt;To understand &lt;em&gt;why&lt;/em&gt; Fiber was necessary — not just useful, but &lt;em&gt;necessary&lt;/em&gt; — we need to start where the React team started. This entire article is grounded in Dan Abramov's talk at JSConf Iceland 2018. If you want to watch it alongside reading, here it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎬 &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4" rel="noopener noreferrer"&gt;Dan Abramov — Beyond React 16 | JSConf Iceland 2018&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's get into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ The Core Question
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"With vast differences in computing power and network speed, how do we deliver the best user experience for everyone?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't a warm-up. It's the exact design challenge the React team was solving. Think about who uses your app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A developer on a MacBook Pro with fiber internet&lt;/li&gt;
&lt;li&gt;A user in rural Southeast Asia on a 3-year-old Android phone with a 3G connection&lt;/li&gt;
&lt;li&gt;Someone on a mid-range laptop on a crowded café WiFi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React runs on all of these. And in 2017, it was doing a poor job of adapting to any of them. The problems fell into two distinct categories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2k6zigac8v0u9ms84yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2k6zigac8v0u9ms84yj.png" alt="CPU/IO → solutions → Fiber" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🖥️ CPU Problems — The Cost of Rendering
&lt;/h2&gt;

&lt;p&gt;These are problems caused by &lt;strong&gt;expensive computation&lt;/strong&gt;. When React updates your UI, it has to do real work: creating DOM nodes, calling render functions, reconciling old and new trees, applying changes to the DOM. On a powerful machine, this might take 5ms. On a low-powered mobile device, the exact same work might take 50ms or more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU problems look like:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A complex chart re-rendering while you're typing&lt;/li&gt;
&lt;li&gt;Mounting a large component tree on initial load&lt;/li&gt;
&lt;li&gt;Re-rendering a long list when one item changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌐 IO Problems — The Cost of Waiting
&lt;/h2&gt;

&lt;p&gt;These are problems caused by &lt;strong&gt;time spent waiting for things to arrive&lt;/strong&gt; — data from an API, a JavaScript bundle from a CDN, an image. The code is fine. The device is fine. You're just waiting on the network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IO problems look like:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data-fetching waterfalls — fetch A, then B, then C, each blocking the next&lt;/li&gt;
&lt;li&gt;Code splitting — loading a bundle before a page can render&lt;/li&gt;
&lt;li&gt;Showing wrong loading states because data arrived faster or slower than expected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both needed new solutions. And as we'll see, both solutions required the exact same capability from React's engine. But first — let's see the problem in action.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎬 The Demo That Changed Everything
&lt;/h2&gt;

&lt;p&gt;The best way to feel this problem is to see it. Jump to &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=177s" rel="noopener noreferrer"&gt;2:57 in Dan's talk&lt;/a&gt; — this is the moment the demo lands.&lt;/p&gt;

&lt;p&gt;The app: an input field at the top, a detailed chart below. The rule: &lt;strong&gt;the more characters you type, the more detailed the chart gets.&lt;/strong&gt; Both updates — the input and the chart — happen at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6xopfgc7cz4e3v6w2ze.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6xopfgc7cz4e3v6w2ze.gif" alt="The Demo That Changed Everything" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch the input. There's a clear delay between pressing a key and seeing the character appear. The browser is so busy computing the chart that &lt;strong&gt;it can't process keystrokes fast enough&lt;/strong&gt;. The thread is blocked for the entire duration of the render — which might be 50, 80, even 100ms — and the user feels every millisecond of it.&lt;/p&gt;

&lt;p&gt;Dan put the root problem precisely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The underlying problem is that the update is big and synchronous — once React starts rendering, it cannot stop."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is visible directly in the React source code. In synchronous mode, the work loop looks like this:&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;// From React source — synchronous mode&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;workLoopSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;performUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&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;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwf43ano0uov6wltkp8xn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwf43ano0uov6wltkp8xn.png" alt="workLoopSync vs workLoopConcurrent" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a plain &lt;code&gt;while&lt;/code&gt; loop with no exit condition other than finishing everything. Once React starts, it runs to completion. There is no way to pause it from the outside.&lt;/p&gt;




&lt;h2&gt;
  
  
  🩹 The Attempted Fix: Debouncing
&lt;/h2&gt;

&lt;p&gt;The most natural workaround: instead of updating the chart on every keystroke, wait until the user pauses typing, then do one big update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvndsfxlscsvgx9q47sui.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvndsfxlscsvgx9q47sui.gif" alt="debounced version — input is responsive but chart lags, feels disconnected" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Better? A little. But it has three real problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It's not adaptive to the device.&lt;/strong&gt; Debouncing uses a fixed delay — say 300ms. If the user's machine is powerful enough to render instantly, they still wait 300ms. If it's slow, 300ms might not even be enough time for the render to finish without freezing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It still locks up when it fires.&lt;/strong&gt; Try enabling CPU throttling in DevTools (4–6x slowdown, simulating a low-end phone). Even with debouncing, the moment the update fires, the browser locks up completely. You've moved the jank — not eliminated it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It doesn't help with mounting.&lt;/strong&gt; Debouncing can delay an &lt;em&gt;update&lt;/em&gt;, but when you first &lt;em&gt;mount&lt;/em&gt; a large component tree, there's nothing to debounce. React mounts it all at once, synchronously. During that time, nothing in the page is interactive — click events don't register, animations freeze.&lt;/p&gt;

&lt;p&gt;The problem isn't &lt;em&gt;when&lt;/em&gt; the update fires. It's that the update is &lt;strong&gt;synchronous and uninterruptible&lt;/strong&gt; when it does.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 What If React Could Pause?
&lt;/h2&gt;

&lt;p&gt;What if React could start rendering the chart update, notice that a keystroke arrived, &lt;strong&gt;pause the render&lt;/strong&gt;, handle the keystroke immediately, then &lt;strong&gt;resume&lt;/strong&gt; from exactly where it stopped?&lt;/p&gt;

&lt;p&gt;The total amount of work is identical. But the &lt;em&gt;experience&lt;/em&gt; is completely different — because users get immediate feedback on their input while the expensive work finishes quietly in the background.&lt;/p&gt;

&lt;p&gt;To understand why this requires a completely new engine, we need to talk about frames.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎞️ The 16ms Frame Budget
&lt;/h2&gt;

&lt;p&gt;Browsers aim to run at &lt;strong&gt;60 frames per second&lt;/strong&gt;. The math:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000ms ÷ 60 frames ≈ 16.67ms per frame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ~16ms, the browser needs to execute JavaScript, recalculate styles, lay out the page, and paint pixels to the screen. If JavaScript takes more than ~16ms in a continuous burst, the browser &lt;strong&gt;misses that frame&lt;/strong&gt;. You see jank — stuttering, freezing, lag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaoulrh9naj0jdwigd20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaoulrh9naj0jdwigd20.png" alt="frame budget — one long sync task drops frames vs sliced work fits each frame" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Old React had no awareness of this budget at all. A single render could take 100ms and React would march through it, never yielding control, never letting the browser breathe.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⏱️ Time Slicing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Time Slicing&lt;/strong&gt; is React's solution to the CPU problem.&lt;/p&gt;

&lt;p&gt;The core idea: instead of doing all rendering in one continuous chunk, React &lt;strong&gt;slices that work into small pieces&lt;/strong&gt; and checks between each piece whether something more urgent has arrived. This is exactly what the concurrent version of the work loop does differently:&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;// From React source — concurrent mode&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;workLoopConcurrent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;shouldYield&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;performUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workInProgress&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;Spot the difference from &lt;code&gt;workLoopSync&lt;/code&gt;: &lt;code&gt;!shouldYield()&lt;/code&gt;. After every single unit of work, React asks the Scheduler: &lt;em&gt;"Is it time to yield?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the actual &lt;code&gt;shouldYield&lt;/code&gt; logic from the React Scheduler source:&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;shouldYieldToHost&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;timeElapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCurrentTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&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;timeElapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;frameInterval&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// still have budget — keep working&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// time's up — yield to the browser&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each task is given about 5ms. When that time is up, React pauses. The browser gets a chance to handle user input and paint. Then React resumes from exactly where it left off.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foewlalvgos9h9gi0d51c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foewlalvgos9h9gi0d51c.png" alt="async version — input updates instantly on every keystroke, chart renders gradually in background" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The input updates &lt;strong&gt;immediately&lt;/strong&gt; on every keystroke. The chart still renders — it just does so across multiple frames instead of one big block. The total work is the same. The experience is night and day.&lt;/p&gt;

&lt;p&gt;Dan described the properties precisely at JSConf Iceland:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We've built a generic way to ensure that high-priority updates don't get blocked by a low-priority update, called time slicing. If my device is fast enough, it feels almost like it's synchronous; if my device is slow, the app still feels responsive. It adapts to the device thanks to the requestIdleCallback API. Notice that only the final state was displayed — the rendered screen is always consistent and we don't see visual artifacts of slow rendering causing a janky user experience."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three properties to lock in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feels synchronous on fast devices&lt;/strong&gt; — slices are so small and fast you never notice them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feels responsive on slow devices&lt;/strong&gt; — high-priority work (keystrokes) always gets through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the final state is shown&lt;/strong&gt; — no intermediate render states flash on screen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌿 The Git Metaphor
&lt;/h3&gt;

&lt;p&gt;Dan used a metaphor in his talk that makes prioritization intuitive.&lt;/p&gt;

&lt;p&gt;Without time slicing, React is like working directly on &lt;code&gt;main&lt;/code&gt;. An urgent bug comes in mid-feature — you can't fix it. You have to finish the feature first.&lt;/p&gt;

&lt;p&gt;With time slicing, React works like a proper branching workflow. You start on a feature branch — the low-priority chart render. An urgent task arrives — a keystroke. You handle it on &lt;code&gt;main&lt;/code&gt; first, then rebase your feature branch on top when you're done.&lt;/p&gt;

&lt;p&gt;React does the rebasing automatically. The low-priority render continues from where it stopped, now applied on top of the most current state. You never think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fov37mn9ze7ry2fdzamrf.png" alt="Git branch metaphor — sync vs async prioritization" width="800" height="148"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  ⏳ Suspense
&lt;/h2&gt;

&lt;p&gt;If Time Slicing solves the CPU side, &lt;strong&gt;Suspense&lt;/strong&gt; solves the IO side.&lt;/p&gt;

&lt;p&gt;Dan introduced it this way:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We've built a generic way for components to suspend rendering while they load async data, which we call suspense. You can pause any state update until the data is ready, and you can add async loading to any component deep in the tree without plumbing all the props and state through your app and hoisting the logic."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The concept: instead of manually managing loading states with &lt;code&gt;isLoading&lt;/code&gt; flags and scattered &lt;code&gt;useEffect&lt;/code&gt; fetches, a component can &lt;strong&gt;declare that it's waiting&lt;/strong&gt; and React handles the rest.&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;function&lt;/span&gt; &lt;span class="nf"&gt;MovieDetails&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="c1"&gt;// If data isn't cached yet, this suspends — throws a Promise&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;movieCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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="k"&gt;return&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;movie&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;div&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&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;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MovieDetails&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, when a component suspends, it throws a &lt;code&gt;Promise&lt;/code&gt;. React catches it at the nearest &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundary, shows the fallback, and retries the render when the Promise resolves. We'll go deep on the exact mechanics — including how this connects to algebraic effects — in Part 4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8awws1n1vc4vivx0c74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8awws1n1vc4vivx0c74.png" alt="Suspense mechanism — throw Promise, catch at boundary, retry on resolve" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's powerful is how it adapts to network conditions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"On a fast network, updates appear very fluid and instantaneous without a jarring cascade of spinners that appear and disappear. On a slow network, you can intentionally design which loading states the user should see and how granular or coarse they should be, instead of showing spinners based on how the code is written. The app stays responsive throughout."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the key point Dan closed with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3oxpl0m3oqn9kmzi385.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3oxpl0m3oqn9kmzi385.png" alt="Suspense demo — navigating the app while a subtree loads in background, no spinner waterfall" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Importantly, this is still the React you know. This is still the declarative component paradigm that you probably like about React."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The mental model for you as a developer doesn't change. The engine underneath does.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 The Common Thread: Interrupting
&lt;/h2&gt;

&lt;p&gt;Here's what ties both features together.&lt;/p&gt;

&lt;p&gt;Time Slicing and Suspense look completely different — one is about CPU, one is about IO. But they share &lt;strong&gt;one fundamental requirement:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;React needs to be able to interrupt what it's doing and resume it later.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Time Slicing: interrupt to yield within the 16ms frame budget&lt;/li&gt;
&lt;li&gt;Suspense: interrupt to wait for async data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same capability. This is exactly why React needed Fiber.&lt;/p&gt;

&lt;p&gt;The old React engine used JavaScript's native call stack to track all rendering work. And the native call stack has a hard limitation: &lt;strong&gt;you cannot pause it from the outside.&lt;/strong&gt; Once a stack of function calls starts executing, it runs to completion. There is no JavaScript API to say "stop here, do something else, resume from this exact frame."&lt;/p&gt;

&lt;p&gt;Matheus Albuquerque explained the solution beautifully in his React Summit 2022 talk:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We can think of the Fiber architecture as a React-specific call stack model that gives full control over scheduling of what should be done. And a Fiber itself is basically a stack frame for a given React component."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And crucially — unlike native stack frames, Fibers are just plain JavaScript objects. React can create them, inspect them, pause them, delete them, and resume them whenever it wants. Sam Galson put the key insight in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Fiber is a reimplementation of the stack, specialized for React components. The advantage of reimplementing the stack is that you can keep stack frames in memory and execute them however — and whenever — you want."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr19bvfw8ma3glpvp5dui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr19bvfw8ma3glpvp5dui.png" alt="native JS call stack (opaque, uninterruptible" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives us React's internal pipeline, as described by &lt;a href="https://jser.dev/2023-07-11-overall-of-react-internals/" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt; — something changes (setState, event). React registers the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; — the Scheduler, a priority queue, decides &lt;em&gt;when&lt;/em&gt; and in what order work happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; — React walks the Fiber tree and figures out what changed. &lt;strong&gt;This phase is interruptible.&lt;/strong&gt; This is where Time Slicing and Suspense live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt; — React applies changes to the real DOM. Synchronous. Cannot be interrupted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqhwd3ih47dszuwxhdir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqhwd3ih47dszuwxhdir.png" alt="React 4 phases — Trigger → Schedule → Render → Commit" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fiber makes the Render phase interruptible. Everything else follows from that.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗺️ What's Coming in This Series
&lt;/h2&gt;

&lt;p&gt;This was the &lt;em&gt;why&lt;/em&gt;. The rest of the series is the &lt;em&gt;how&lt;/em&gt; — each part built around one problem and one solution.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Core story&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Why React Had to Build Its Own Execution Engine&lt;/td&gt;
&lt;td&gt;JS call stack can't pause → React builds Fiber + Scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How React Finds What Actually Changed&lt;/td&gt;
&lt;td&gt;Re-rendering everything is too slow → the Reconciler + keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Idea That Makes Suspense Possible&lt;/td&gt;
&lt;td&gt;Algebraic effects → how Suspense and ErrorBoundary actually work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The React Lifecycle From the Inside&lt;/td&gt;
&lt;td&gt;When &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useLayoutEffect&lt;/code&gt; fire and why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How State Actually Works&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useRef&lt;/code&gt;, dispatch, and the commit state phase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Trap of Vibe Coding &lt;code&gt;useCallback&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;What causes re-renders — before reaching for &lt;code&gt;memo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server Components &amp;amp; Hydration: The Real Story&lt;/td&gt;
&lt;td&gt;How React moved rendering to the server — and what it cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Complete Picture: From a Keystroke to a Pixel&lt;/td&gt;
&lt;td&gt;Every layer of React working together as one system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎬 Watch These Talks
&lt;/h2&gt;

&lt;p&gt;This article is a distillation. The primary sources are worth your time — here's what to watch and why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dan Abramov — &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4" rel="noopener noreferrer"&gt;Beyond React 16 | JSConf Iceland 2018&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(33 min)&lt;/em&gt;&lt;br&gt;
The talk this entire article is based on. Key moments: the demo at &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=177s" rel="noopener noreferrer"&gt;2:57&lt;/a&gt;, the async version revealed at &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=370s" rel="noopener noreferrer"&gt;6:10&lt;/a&gt;, the Git metaphor at &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=496s" rel="noopener noreferrer"&gt;8:16&lt;/a&gt;, and the Suspense demo from &lt;a href="https://www.youtube.com/watch?v=nLF0n9SACd4&amp;amp;t=940s" rel="noopener noreferrer"&gt;15:40&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matheus Albuquerque — &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4" rel="noopener noreferrer"&gt;Inside Fiber: the In-Depth Overview | React Summit 2022&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(27 min)&lt;/em&gt;&lt;br&gt;
The deepest walkthrough of FiberNode internals I've found. Start at &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4&amp;amp;t=131s" rel="noopener noreferrer"&gt;2:11&lt;/a&gt; for the Fiber-as-call-stack explanation that directly connects to Part 2 of this series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sam Galson — &lt;a href="https://www.youtube.com/watch?v=5OPyjrKytLU" rel="noopener noreferrer"&gt;Magic in the web: coroutines, continuations, fibers | React Advanced London&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(~30 min)&lt;/em&gt;&lt;br&gt;
The computer science behind React's approach — coroutines, continuations, algebraic effects, and why Fiber is the shape it is. Essential background for Parts 2 and 3.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Sources &amp;amp; Thanks
&lt;/h2&gt;

&lt;p&gt;A huge thank you to everyone whose work made this article possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dan Abramov&lt;/strong&gt; — for Beyond React 16 and for the Git metaphor that makes Time Slicing click&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matheus Albuquerque&lt;/strong&gt; — for the clearest explanation of FiberNode internals at React Summit 2022. Transcript via &lt;a href="https://gitnation.com/contents/inside-fiber-the-in-depth-overview-you-wanted-a-tldr-for" rel="noopener noreferrer"&gt;gitnation.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sam Galson&lt;/strong&gt; — for connecting React to the wider CS theory of fibers, coroutines, and continuations. Full article: &lt;a href="https://medium.com/yld-blog/continuations-coroutines-fibers-effects-e163dda9dedc" rel="noopener noreferrer"&gt;Continuations, coroutines, fibers, effects (YLD Blog)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sophie Alpert &amp;amp; the React team&lt;/strong&gt; — &lt;a href="https://legacy.reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html" rel="noopener noreferrer"&gt;Sneak Peek: Beyond React 16 (React Blog)&lt;/a&gt; — the official writeup with Dan's exact quotes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jser.dev" rel="noopener noreferrer"&gt;jser.dev&lt;/a&gt;&lt;/strong&gt; — the best source-level analysis of React internals on the internet. The &lt;code&gt;workLoopSync&lt;/code&gt;, &lt;code&gt;workLoopConcurrent&lt;/code&gt;, &lt;code&gt;shouldYieldToHost&lt;/code&gt;, and the 4-phase model in this article all come from JSer's series — especially &lt;a href="https://jser.dev/2023-07-11-overall-of-react-internals/" rel="noopener noreferrer"&gt;the overview&lt;/a&gt;, &lt;a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works/" rel="noopener noreferrer"&gt;the scheduler breakdown&lt;/a&gt;, and &lt;a href="https://jser.dev/react/2022/03/26/lanes-in-react/" rel="noopener noreferrer"&gt;the Lanes deep dive&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.lydiahallie.io/" rel="noopener noreferrer"&gt;Lydia Hallie&lt;/a&gt;&lt;/strong&gt; — for in-row and non-in-row fetching explanations and for the JavaScript visualizations that shaped this series' style&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Part 2 is next — JavaScript's call stack is why React couldn't do any of this. Here's what React built instead. 🔧&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>suspense</category>
      <category>fiber</category>
    </item>
    <item>
      <title>The Full Architecture: How a Form-JS Extension System Fits Together</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Wed, 29 Apr 2026 06:56:00 +0000</pubDate>
      <link>https://forem.com/samabaasi/the-full-architecture-how-a-form-js-extension-system-fits-together-588h</link>
      <guid>https://forem.com/samabaasi/the-full-architecture-how-a-form-js-extension-system-fits-together-588h</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 23 of the series: "Extending bpmn-io Form-JS Beyond Its Limits"&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This is the last article in the series. It's also the one to read first.&lt;/p&gt;

&lt;p&gt;If you've arrived here without reading the others, this article will show you the complete architecture so you can identify which pieces you need and navigate directly to the relevant articles. If you've read all twenty-two articles before this one, this article assembles them into a coherent whole — showing how every piece connects to every other piece and where the system's decisions were made deliberately versus where they were made under pressure and later regretted.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Built
&lt;/h2&gt;

&lt;p&gt;Over twenty-two articles, the system grew to this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 evaluators&lt;/strong&gt; — classes that run on every &lt;code&gt;changed&lt;/code&gt; event and maintain runtime state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BindingEvaluator&lt;/code&gt; — computes derived field values from FEEL expressions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HideEvaluator&lt;/code&gt; — toggles field visibility based on FEEL conditions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DisabledEvaluator&lt;/code&gt; — applies disabled state from expressions or static flags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RequiredEvaluator&lt;/code&gt; — enforces dynamic required fields and mutates the schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PersistentEvaluator&lt;/code&gt; — marks fields for application-layer persistence&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; — validates datetime fields with scoped re-evaluation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TicketAutoFillEvaluator&lt;/code&gt; — async field population from external APIs with caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;12 properties panel providers&lt;/strong&gt; — classes that extend the form editor's configuration UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DisabledPropertiesProvider&lt;/code&gt;, &lt;code&gt;ReadonlyPropertiesProvider&lt;/code&gt;, &lt;code&gt;RequiredPropertiesProvider&lt;/code&gt; — override built-in properties with FEEL-capable versions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FeelExpressionPropertiesProvider&lt;/code&gt; — adds FEEL binding entry to Form Logics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HideIfPropertiesProvider&lt;/code&gt; — adds hide-if condition entry to Form Logics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PersistentPropertiesProvider&lt;/code&gt; — adds persistent flag entry to Form Logics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShowLatestValuePropertiesProvider&lt;/code&gt; — adds show-latest-value entry to Form Logics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TicketAutoFillPropertiesProvider&lt;/code&gt; — adds cascading API-backed configuration UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DropdownPropertiesProvider&lt;/code&gt; — replaces simple dropdown with full configuration panel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GridFieldPropertiesProvider&lt;/code&gt; — adds grid-specific validation rule configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DateTimePropertiesPanelExtension&lt;/code&gt; — configures datetime replacement field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FileUploadPropertiesProvider&lt;/code&gt; — adds file validation configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3 custom validators&lt;/strong&gt; — classes that extend &lt;code&gt;form.validate()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FeelValidator&lt;/code&gt; — FEEL expression-based field validation at submit time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GridFieldValidationValidator&lt;/code&gt; — validates grid cell contents on submit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RequiredValidator&lt;/code&gt; — enforces dynamic required state at submit time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5 custom field renderers&lt;/strong&gt; — registered for custom or replaced field types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DropdownFieldRenderer&lt;/code&gt; — bridges five React dropdown components into Preact&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GridFieldRenderer&lt;/code&gt; — full custom grid with Excel import, validation, and formulas&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DateTimeFieldRenderer&lt;/code&gt; — replaces built-in datetime with rsuite DatePicker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FileUploadFieldRenderer&lt;/code&gt; — three-stage file handling with Camunda attachment upload&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ImageViewFieldRenderer&lt;/code&gt; — custom image display field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CustomForm&lt;/code&gt; — subclasses &lt;code&gt;Form&lt;/code&gt;, bootstraps all modules, fires &lt;code&gt;form.rendered&lt;/code&gt;, initializes &lt;code&gt;_pendingFilesRef&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CustomFormEditor&lt;/code&gt; — subclasses &lt;code&gt;FormEditor&lt;/code&gt;, bootstraps all editor modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SearchableSelect&lt;/code&gt; — Preact searchable dropdown used throughout the properties panel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PANEL_GROUPS&lt;/code&gt; and &lt;code&gt;FORM_EVENTS&lt;/code&gt; constants — shared registries for group IDs and event names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;excelUtils&lt;/code&gt; — shared import/export utility for label/value pairs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The System Architecture
&lt;/h2&gt;

&lt;p&gt;Here is every component and how they connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpf5h7tck2soccgv7mwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpf5h7tck2soccgv7mwo.png" alt="The System Architecture"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Form Lifecycle, Step by Step
&lt;/h2&gt;

&lt;p&gt;Every form session follows this sequence. Here is each step with the relevant article:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Instantiation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// additionalModules declared inside CustomForm constructor&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;CustomForm&lt;/code&gt; (Article 21) calls &lt;code&gt;super()&lt;/code&gt; with a merged options object that includes all modules. The DI container is created. All services declared in &lt;code&gt;__init__&lt;/code&gt; are instantiated immediately. Evaluators and validators run their constructors, subscribing to events.&lt;/p&gt;

&lt;p&gt;At the end of the constructor, &lt;code&gt;CustomForm&lt;/code&gt; attaches &lt;code&gt;_pendingFilesRef&lt;/code&gt; to the event bus instance (Article 15, 18).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening invisibly:&lt;/strong&gt; The DI container resolves all &lt;code&gt;$inject&lt;/code&gt; arrays. &lt;code&gt;RequiredEvaluator.$inject = ['eventBus', 'form']&lt;/code&gt; means the DI container calls &lt;code&gt;new RequiredEvaluator(eventBus, form)&lt;/code&gt;. If &lt;code&gt;$inject&lt;/code&gt; is missing or has a typo, the parameter is &lt;code&gt;undefined&lt;/code&gt; — the most common error in Form-JS extension development (Article 1).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Schema Import
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Form-JS reads the schema JSON and creates the Preact component tree. For each field, it looks up the registered renderer in &lt;code&gt;formFields&lt;/code&gt; — your replacements (Article 17) win because last-registration-wins (Article 2).&lt;/p&gt;

&lt;p&gt;The field renderers render their Preact output. For dropdown fields, the Preact renderer renders a &lt;code&gt;&amp;lt;div id="..."&amp;gt;&lt;/code&gt; mount point and calls &lt;code&gt;createRoot()&lt;/code&gt; to mount the React component inside it (Article 9). The &lt;code&gt;reactRoots&lt;/code&gt; Map tracks the root for lifecycle management.&lt;/p&gt;

&lt;p&gt;After &lt;code&gt;super.importSchema()&lt;/code&gt; completes, &lt;code&gt;CustomForm.importSchema&lt;/code&gt; fires &lt;code&gt;form.rendered&lt;/code&gt; (Article 15). This signals that the DOM is ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Evaluators Initialize
&lt;/h3&gt;

&lt;p&gt;Each evaluator's &lt;code&gt;form.init&lt;/code&gt; handler fires. The evaluators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;this._initialized = true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;evaluateAll()&lt;/code&gt; after a 50ms delay&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TicketAutoFillEvaluator&lt;/code&gt; calls &lt;code&gt;_buildWatchMap()&lt;/code&gt; to scan the schema (Article 20)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; calls &lt;code&gt;_buildDatetimeFieldRegistry()&lt;/code&gt; to build its Set (Article 22)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial &lt;code&gt;evaluateAll()&lt;/code&gt; run applies the current state of all expressions against the pre-populated data. Fields with &lt;code&gt;disabled: "= status = 'closed'"&lt;/code&gt; get the &lt;code&gt;disabled&lt;/code&gt; attribute applied. Fields with &lt;code&gt;conditional.hide: "= role != 'admin'"&lt;/code&gt; get &lt;code&gt;display: none&lt;/code&gt; applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: External Context (Optional)
&lt;/h3&gt;

&lt;p&gt;If the form needs ticket data for auto-fill, the application fires it into the form after import:&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;const&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eventBus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ticket.context.set&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="na"&gt;ticket_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentTicket&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="na"&gt;ticket_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentTicket&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;TicketAutoFillEvaluator&lt;/code&gt; receives this via its &lt;code&gt;ticket.context.set&lt;/code&gt; listener (Article 15, 20).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: User Interaction
&lt;/h3&gt;

&lt;p&gt;The user types in a field. Form-JS updates form state. &lt;code&gt;changed&lt;/code&gt; fires with the changed field's data.&lt;/p&gt;

&lt;p&gt;Each evaluator's &lt;code&gt;changed&lt;/code&gt; handler fires:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unscoped evaluators&lt;/strong&gt; (Article 8): &lt;code&gt;DisabledEvaluator&lt;/code&gt;, &lt;code&gt;HideEvaluator&lt;/code&gt;, &lt;code&gt;RequiredEvaluator&lt;/code&gt;, &lt;code&gt;PersistentEvaluator&lt;/code&gt;, &lt;code&gt;BindingEvaluator&lt;/code&gt; — all check &lt;code&gt;_evaluating&lt;/code&gt; and proceed if clear. After a 10ms debounce, &lt;code&gt;evaluateAll()&lt;/code&gt; runs. Each checks all components, finds ones with FEEL expressions, evaluates against current form data, detects changes, updates DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoped evaluators&lt;/strong&gt; (Article 22): &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; checks whether the changed field is in &lt;code&gt;_datetimeFieldKeys&lt;/code&gt;. If not, it returns immediately. If yes, it runs evaluation only for datetime fields with changed values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;TicketAutoFillEvaluator&lt;/code&gt;&lt;/strong&gt; (Article 20): checks whether the changed field is in &lt;code&gt;_watchedFields&lt;/code&gt;. If not, returns immediately. If yes, and the ticket selection changed, resets dependent fields, fetches from API (with cache), evaluates conditions, formats values, writes results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Properties Panel Changes (Editor Only)
&lt;/h3&gt;

&lt;p&gt;In the form editor, when the form designer changes a property:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;editField(field, path, value)&lt;/code&gt; is called — updates the schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;propertiesPanel.updated&lt;/code&gt; fires&lt;/li&gt;
&lt;li&gt;All providers' &lt;code&gt;getGroups&lt;/code&gt; functions run with the updated field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getOrCreateFormLogicsGroup()&lt;/code&gt; is called by each provider — creates or finds the Form Logics group (Article 19)&lt;/li&gt;
&lt;li&gt;Override providers filter out replaced entries (Article 6)&lt;/li&gt;
&lt;li&gt;All providers add their entries to the appropriate groups&lt;/li&gt;
&lt;li&gt;The panel re-renders with the updated configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the change was to a FEEL expression (say, changing &lt;code&gt;disabledExpression&lt;/code&gt;), the dual-path &lt;code&gt;setValue&lt;/code&gt; stores it in the schema (Article 7). On the next &lt;code&gt;changed&lt;/code&gt; event, &lt;code&gt;DisabledEvaluator&lt;/code&gt; reads &lt;code&gt;disabledExpression&lt;/code&gt;, evaluates it, and applies the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Validation on Submit
&lt;/h3&gt;

&lt;p&gt;The user clicks Submit. Form-JS calls &lt;code&gt;form.validate()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because three validators have wrapped &lt;code&gt;form.validate()&lt;/code&gt; with the merge-errors pattern (Article 10):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form.validate() called
  → RequiredValidator's wrapper runs
    → original validate() called
      → FeelValidator's wrapper runs
        → original validate() called
          → GridFieldValidationValidator's wrapper runs
            → actual Form-JS validate() runs
            ← returns built-in errors
          ← GridFieldValidationValidator merges grid errors
        ← FeelValidator merges FEEL errors
      ← merge
    ← RequiredValidator merges dynamic required errors
  ← return merged errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The merged errors object is written to form state via &lt;code&gt;_setState({ errors })&lt;/code&gt;. Fields with errors get red borders and error messages below them.&lt;/p&gt;

&lt;p&gt;If errors exist, submission stops. If no errors, the application proceeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: File Upload and Submission
&lt;/h3&gt;

&lt;p&gt;After successful validation, the application code:&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;// 1. Complete the Camunda task&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;camundaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completeTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Upload files from _pendingFilesRef&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadPendingFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&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;uploadPendingFiles&lt;/code&gt; reads &lt;code&gt;eventBus._pendingFilesRef.current&lt;/code&gt; (the Map populated by &lt;code&gt;FileUpload&lt;/code&gt; React components via &lt;code&gt;useEffect&lt;/code&gt;), and posts each file to &lt;code&gt;/engine-rest/task/${taskId}/attachment&lt;/code&gt; as multipart FormData (Article 18).&lt;/p&gt;

&lt;p&gt;Files are uploaded after task completion — not before. If task completion fails, no orphaned attachments are created.&lt;/p&gt;

&lt;p&gt;After upload, &lt;code&gt;pendingFilesRef.current.clear()&lt;/code&gt; prevents double-upload on re-submit.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Every Article Connects
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Article&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Connects To&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 — Module System&lt;/td&gt;
&lt;td&gt;DI container, &lt;code&gt;$inject&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 — Custom Field Architecture&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GridFieldRenderer&lt;/code&gt;, &lt;code&gt;.config&lt;/code&gt;, &lt;code&gt;formFields.register&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Articles 9, 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 — FEEL Pipeline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;evaluate()&lt;/code&gt;, JS fallback&lt;/td&gt;
&lt;td&gt;Articles 4, 8, 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 — FEEL Context&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prepareContext()&lt;/code&gt;, type coercion&lt;/td&gt;
&lt;td&gt;Articles 8, 13, 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 — Provider Contract&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getGroups&lt;/code&gt;, middleware pattern&lt;/td&gt;
&lt;td&gt;Articles 6–7, 11–12, 19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6 — Overriding Entries&lt;/td&gt;
&lt;td&gt;Filter + replace pattern&lt;/td&gt;
&lt;td&gt;Articles 7, 19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 — Toggle-or-FEEL&lt;/td&gt;
&lt;td&gt;Dual-path getValue/setValue&lt;/td&gt;
&lt;td&gt;Articles 6, 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 — Five Evaluators&lt;/td&gt;
&lt;td&gt;Event subscription, state Map, DOM&lt;/td&gt;
&lt;td&gt;Articles 10, 15, 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 — React/Preact Bridge&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;reactRoots&lt;/code&gt; Map, &lt;code&gt;createRoot&lt;/code&gt;, generation counter&lt;/td&gt;
&lt;td&gt;Articles 17, 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 — Validation Hook&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bind()&lt;/code&gt;, merge-errors, &lt;code&gt;_validating&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Articles 3, 4, 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11 — Dynamic Panels&lt;/td&gt;
&lt;td&gt;Three conditional patterns&lt;/td&gt;
&lt;td&gt;Articles 12, 14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 — Cascading Config&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useEffect&lt;/code&gt; deps, &lt;code&gt;editField&lt;/code&gt;, per-level state&lt;/td&gt;
&lt;td&gt;Articles 11, 14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13 — Conditional Options&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;applyConditionalRules&lt;/code&gt;, form data storage&lt;/td&gt;
&lt;td&gt;Articles 3, 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14 — SearchableSelect&lt;/td&gt;
&lt;td&gt;Preact hooks, &lt;code&gt;onInput&lt;/code&gt;, click-outside&lt;/td&gt;
&lt;td&gt;Articles 11, 12, 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 — Event Bus&lt;/td&gt;
&lt;td&gt;Custom events, &lt;code&gt;_pendingFilesRef&lt;/code&gt;, registry&lt;/td&gt;
&lt;td&gt;Articles 8, 18, 19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 — Excel Import/Export&lt;/td&gt;
&lt;td&gt;ExcelJS, &lt;code&gt;readAsArrayBuffer&lt;/code&gt;, header detection&lt;/td&gt;
&lt;td&gt;Articles 14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17 — DateTime Replacement&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;formFields.register&lt;/code&gt;, container-ref, subtypes&lt;/td&gt;
&lt;td&gt;Articles 2, 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18 — File Handling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;localFiles&lt;/code&gt;, &lt;code&gt;_pendingFilesRef&lt;/code&gt;, &lt;code&gt;uploadFileAsAttachment&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Articles 9, 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19 — Form Logics Group&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getOrCreateFormLogicsGroup&lt;/code&gt;, cooperative providers&lt;/td&gt;
&lt;td&gt;Articles 5, 6, 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 — Async AutoFill&lt;/td&gt;
&lt;td&gt;Watcher map, two-cache, context enrichment&lt;/td&gt;
&lt;td&gt;Articles 3, 4, 8, 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21 — CustomForm/FormEditor&lt;/td&gt;
&lt;td&gt;Subclassing, module bootstrapping&lt;/td&gt;
&lt;td&gt;All articles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22 — Scoped Re-evaluation&lt;/td&gt;
&lt;td&gt;Field-type gate, Set registry, change detection&lt;/td&gt;
&lt;td&gt;Article 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23 — Architecture&lt;/td&gt;
&lt;td&gt;This article&lt;/td&gt;
&lt;td&gt;All articles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Three Things I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I were starting this system from scratch, knowing what I know now, these are the three decisions I'd make differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Extract Shared Utilities Before Writing the Second Provider
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SearchableSelect&lt;/code&gt; component was written five times. The &lt;code&gt;getOrCreateFormLogicsGroup&lt;/code&gt; function was written into six providers separately before being extracted. The FEEL evaluation pipeline was partially duplicated between evaluators. The &lt;code&gt;_getAllComponents&lt;/code&gt; recursive function exists in nearly every evaluator.&lt;/p&gt;

&lt;p&gt;Every duplicate was created with the same justification: "I'm not sure the API is stable yet." It's a rationalization. The real cost of not extracting is not the initial 45 minutes — it's the maintenance cost that accumulates over months. Each bug fix applied to one copy is a bug that survives in three others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule I now follow:&lt;/strong&gt; extract when you copy a second time. Not the fifth.&lt;/p&gt;

&lt;p&gt;For a system this size, the shared utilities that should exist before you write the first provider or evaluator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/formjs/shared/
├── constants.ts          ← PANEL_GROUPS, FORM_EVENTS
├── panelUtils.ts         ← getOrCreateFormLogicsGroup, getOrCreateGroup
├── evaluatorUtils.ts     ← _getAllComponents, _interpretAsBoolean, _evaluateJavaScript
├── feelPipeline.ts       ← evaluate(), prepareContext(), _coerceDateValue()
├── excelUtils.ts         ← importLabelValueFromExcel, exportLabelValueToExcel
└── ui/
    └── SearchableSelect.tsx  ← The reusable component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writing these first — before any evaluator or provider — costs two days. It saves two weeks of cleanup later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fix the React Root Race Condition From the Start
&lt;/h3&gt;

&lt;p&gt;Article 9 documents the generation counter fix for the React/Preact bridge:&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;// When cleanup fires 150ms after unmount,&lt;/span&gt;
&lt;span class="c1"&gt;// check that the generation hasn't changed (no remount happened)&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;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;capturedGeneration&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unmount&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 fix exists because I discovered the race condition three weeks after shipping the bridge. The original code — without the generation counter — caused dropdowns to disappear when Form-JS's schema update cycle unmounted and remounted the Preact renderer within 150ms.&lt;/p&gt;

&lt;p&gt;The symptom was intermittent. It appeared when form designers updated dropdown configuration in the editor. The React root was unmounted by the stale cleanup, and the dropdown field showed a blank mount point with no error.&lt;/p&gt;

&lt;p&gt;The root cause was knowable before shipping. The 150ms deferred cleanup was chosen to give React time to flush — but "give React time" is exactly the language that should raise a red flag. Deferred operations with fixed timeouts have race conditions. The generation counter should have been part of the original design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule I now follow:&lt;/strong&gt; any deferred operation that affects shared state needs a generation counter or equivalent cancellation mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Establish the Constants File Before Writing Any Provider
&lt;/h3&gt;

&lt;p&gt;The Form Logics group ID &lt;code&gt;'form-logics'&lt;/code&gt; was a string literal in six files before I extracted it to &lt;code&gt;PANEL_GROUPS.FORM_LOGICS&lt;/code&gt;. The event name &lt;code&gt;'required.states.changed'&lt;/code&gt; was a string literal in four files. The custom event names were invented ad-hoc with no consistency — some used dots (&lt;code&gt;form.rendered&lt;/code&gt;), some used dots with subcategories (&lt;code&gt;required.states.changed&lt;/code&gt;), one used a colon (&lt;code&gt;dropdown:labelSelected&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;These inconsistencies are not bugs. The system works. But they create friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new developer searching for all uses of the required states event has to search for four different strings&lt;/li&gt;
&lt;li&gt;A typo in any string literal causes a silent failure — no event fires, no error surfaces&lt;/li&gt;
&lt;li&gt;The documentation of which events exist and who fires them is scattered across twelve files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The rule I now follow:&lt;/strong&gt; before writing the first provider or evaluator, create &lt;code&gt;constants.ts&lt;/code&gt;. Add every group ID and every custom event name to it before it's used anywhere. This is a thirty-minute investment that makes the system legible for the lifetime of the codebase.&lt;/p&gt;

&lt;p&gt;The complete constants file should exist as a single source of truth and include:&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="c1"&gt;// The contract for custom behavior&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PANEL_GROUPS&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// All custom panel group IDs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FORM_EVENTS&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// All custom event names&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FIELD_TYPES&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// All custom field type strings&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SUPPORTED_FIELD_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt; &lt;span class="c1"&gt;// Types that accept Form Logics entries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four exports. Thirty minutes. Prevents a category of silent bugs for the lifetime of the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Series Covered That the Docs Don't
&lt;/h2&gt;

&lt;p&gt;The official Form-JS documentation covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to render a form with &lt;code&gt;new Form({ ... })&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The basic JSON schema for field types&lt;/li&gt;
&lt;li&gt;A handful of examples for simple custom fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official documentation does not cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How the DI container works or how to use &lt;code&gt;$inject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to extend the properties panel beyond trivial examples&lt;/li&gt;
&lt;li&gt;How to run FEEL expressions at runtime outside the editor&lt;/li&gt;
&lt;li&gt;How to replace built-in field types&lt;/li&gt;
&lt;li&gt;How to extend &lt;code&gt;form.validate()&lt;/code&gt; without breaking it&lt;/li&gt;
&lt;li&gt;How to coordinate multiple independent providers through a shared group&lt;/li&gt;
&lt;li&gt;How to bridge React components into Preact renderers with production-grade lifecycle management&lt;/li&gt;
&lt;li&gt;How to handle file uploads across framework boundaries&lt;/li&gt;
&lt;li&gt;How to scope re-evaluation for performance in large forms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These twenty-three articles cover the gap between "I can render a form" and "I have a production-grade extension system." That gap is where most developers spend their time and find the fewest answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;The system documented in this series is production-deployed and maintained. The patterns are stable. But they're not the last word — there are at least three extensions that would improve the architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A formal base class for evaluators.&lt;/strong&gt; The six shared methods (&lt;code&gt;_getAllComponents&lt;/code&gt;, &lt;code&gt;_interpretAsBoolean&lt;/code&gt;, &lt;code&gt;_evaluateJavaScript&lt;/code&gt;, &lt;code&gt;prepareContext&lt;/code&gt;, &lt;code&gt;_findFieldElement&lt;/code&gt;, &lt;code&gt;evaluateAll&lt;/code&gt; skeleton) should be in an &lt;code&gt;AbstractEvaluator&lt;/code&gt; class. Each concrete evaluator would extend it and implement only &lt;code&gt;_hasExpression&lt;/code&gt;, &lt;code&gt;_getExpression&lt;/code&gt;, &lt;code&gt;_determineState&lt;/code&gt;, and &lt;code&gt;_applyToDOM&lt;/code&gt;. The current duplication is the most obvious remaining technical debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Web Worker for FEEL evaluation.&lt;/strong&gt; The FEEL pipeline runs on the main thread. For forms with many fields and complex expressions, evaluation adds measurable lag to user interactions. Moving FEEL evaluation to a Web Worker would eliminate main-thread blocking entirely. The API would be asynchronous — evaluators would need to handle Promise-based evaluation results rather than synchronous returns. This is a significant architectural change but would make the system scale to arbitrarily large forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A TypeScript-first rewrite of the properties panel components.&lt;/strong&gt; The properties panel components use Preact's &lt;code&gt;html&lt;/code&gt; tagged template literals and &lt;code&gt;jsx&lt;/code&gt;/&lt;code&gt;jsxs&lt;/code&gt; function calls — not JSX syntax. This makes them hard to read for developers who know React and are learning the Preact properties panel system. A rewrite using Preact's JSX transform with full TypeScript types would make the component code readable, catch type errors at compile time, and make the &lt;code&gt;onInput&lt;/code&gt;/&lt;code&gt;onChange&lt;/code&gt; distinction explicit in the type definitions.&lt;/p&gt;

&lt;p&gt;None of these are urgent. The system works as documented. These are improvements to make the system more maintainable as it grows.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on the Camunda DevEx Application
&lt;/h2&gt;

&lt;p&gt;If you've read this series because you're considering applying to Camunda's Developer Experience team: this is the portfolio.&lt;/p&gt;

&lt;p&gt;Not the articles as writing samples — the engineering decisions documented in the articles. The decision to use the create-if-not-exists pattern for the Form Logics group rather than a central registry. The generation counter fix for the React root race condition. The scoped re-evaluation optimization. The event bus as a cross-framework data store.&lt;/p&gt;

&lt;p&gt;These decisions were made under real constraints — production deadline, legacy code that couldn't be rewritten, browsers that had to be supported. The articles document not just what was built but why each decision was made and what the alternatives cost.&lt;/p&gt;

&lt;p&gt;DevEx work is ultimately about making other developers' decisions easier. The best way to demonstrate that capability is to show that you've made hard decisions yourself, documented them honestly, and built something that other developers can learn from.&lt;/p&gt;

&lt;p&gt;That's what this series is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is Part 23 of "Extending bpmn-io Form-JS Beyond Its Limits." The series covers the complete architecture for production-grade Form-JS extensions — the documentation that doesn't exist yet.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Index of all articles in the series:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The bpmn-io Form-JS Module System: Dependency Injection Nobody Explains&lt;/li&gt;
&lt;li&gt;Building Your First Custom Field in Form-JS: The Complete Four-Layer Architecture&lt;/li&gt;
&lt;li&gt;FEEL at Runtime in Form-JS: Building an Expression Evaluation Pipeline from Scratch&lt;/li&gt;
&lt;li&gt;Preparing a FEEL Context: The Type Coercion Problem Nobody Warns You About&lt;/li&gt;
&lt;li&gt;The Properties Panel Provider Contract: What the Official Docs Leave Out&lt;/li&gt;
&lt;li&gt;Overriding Default Properties Panel Entries: How to Replace What Form-JS Ships With&lt;/li&gt;
&lt;li&gt;The Toggle-or-FEEL Pattern: Properties That Can Be Static or Dynamic&lt;/li&gt;
&lt;li&gt;Five Evaluators, One Pattern: Scaling Conditional Logic Across a Form&lt;/li&gt;
&lt;li&gt;React Inside Preact: Mounting React Components in a Form-JS Renderer&lt;/li&gt;
&lt;li&gt;Hooking Into Form Validation Without Breaking It: The Merge-Errors Pattern&lt;/li&gt;
&lt;li&gt;Dynamic Properties Panels: Three Patterns for Conditional Entry Display&lt;/li&gt;
&lt;li&gt;Cascading Configuration UI: Building Dependent Selection Chains in the Properties Panel&lt;/li&gt;
&lt;li&gt;The Conditional Options Algorithm: Priority Mode vs Merge Mode&lt;/li&gt;
&lt;li&gt;Building a Searchable Select Component for the bpmn-io Properties Panel&lt;/li&gt;
&lt;li&gt;Using the Form-JS Event Bus as an Application Communication Layer&lt;/li&gt;
&lt;li&gt;Excel Import and Export in the bpmn-io Properties Panel&lt;/li&gt;
&lt;li&gt;Replacing a Built-In Field Type: Swapping Form-JS DateTime with rsuite DatePicker&lt;/li&gt;
&lt;li&gt;File Handling Across the Form Lifecycle: From Selection to Camunda Task Attachment&lt;/li&gt;
&lt;li&gt;The Form Logics Group: Building a Cross-Provider Panel Section&lt;/li&gt;
&lt;li&gt;Async AutoFill With Caching: Filling Form Fields From External APIs at Runtime&lt;/li&gt;
&lt;li&gt;Subclassing Form and FormEditor: Building a Custom Runtime Foundation&lt;/li&gt;
&lt;li&gt;Scoped Re-evaluation: Preventing Unnecessary FEEL Expression Evaluation in Large Forms&lt;/li&gt;
&lt;li&gt;The Full Architecture: How a Form-JS Extension System Fits Together&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;camunda&lt;/code&gt; &lt;code&gt;bpmn&lt;/code&gt; &lt;code&gt;formjs&lt;/code&gt; &lt;code&gt;architecture&lt;/code&gt; &lt;code&gt;series-capstone&lt;/code&gt; &lt;code&gt;javascript&lt;/code&gt; &lt;code&gt;devex&lt;/code&gt;&lt;/p&gt;

</description>
      <category>camunda</category>
      <category>javascript</category>
      <category>bpmnio</category>
      <category>formjs</category>
    </item>
    <item>
      <title>Scoped Re-evaluation: Preventing Unnecessary FEEL Expression Evaluation in Large Forms</title>
      <dc:creator>Sam Abaasi</dc:creator>
      <pubDate>Wed, 29 Apr 2026 06:33:11 +0000</pubDate>
      <link>https://forem.com/samabaasi/scoped-re-evaluation-preventing-unnecessary-feel-expression-evaluation-in-large-forms-4k2g</link>
      <guid>https://forem.com/samabaasi/scoped-re-evaluation-preventing-unnecessary-feel-expression-evaluation-in-large-forms-4k2g</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 22 of the series: "Extending bpmn-io Form-JS Beyond Its Limits"&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The evaluator pattern from Article 8 has a performance assumption built into it: when &lt;code&gt;changed&lt;/code&gt; fires, re-evaluate everything. For small forms this is fine. For a form with 50 fields that loads pre-populated data, it's a problem.&lt;/p&gt;

&lt;p&gt;When Form-JS loads pre-populated data, it writes each field's value into form state. Each write fires a &lt;code&gt;changed&lt;/code&gt; event. A 50-field form fires 50 &lt;code&gt;changed&lt;/code&gt; events on load. If you have five evaluators, each one re-evaluating all 50 components on every event, that's 50 × 5 × 50 = 12,500 evaluations on load. Most of them evaluate the same expressions against the same data they already evaluated 10ms ago.&lt;/p&gt;

&lt;p&gt;This article documents the &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; as a case study in scoped re-evaluation — an evaluator that fires only when relevant fields change, filters its context to only relevant data, and skips &lt;code&gt;_setState&lt;/code&gt; calls when nothing changed. Then it generalizes the pattern to a template you can apply to any type-specific evaluator.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem at Scale
&lt;/h2&gt;

&lt;p&gt;To understand why scope matters, trace what happens when a 50-field form loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Form loads with pre-populated data
  → form.init fires (evaluators initialize)
  → Form-JS writes field 1 value → changed fires
     DisabledEvaluator.evaluateAll() runs: 50 components × FEEL eval = 50 evals
     HideEvaluator.evaluateAll() runs: 50 components × FEEL eval = 50 evals
     RequiredEvaluator.evaluateAll() runs: 50 components × FEEL eval = 50 evals
     BindingEvaluator.evaluateAll() runs: 50 components × FEEL eval = 50 evals
     DateTimeValidationEvaluator.evaluateAll() runs: 50 components × FEEL eval = 50 evals
     Total: 250 FEEL evaluations
  → Form-JS writes field 2 value → changed fires
     Same: 250 FEEL evaluations
  ...
  → Form-JS writes field 50 value → changed fires
     Same: 250 FEEL evaluations

Total on load: 50 events × 250 evaluations = 12,500 FEEL evaluations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FEEL evaluation is not free. The &lt;code&gt;feelin&lt;/code&gt; library parses the expression, builds an AST, evaluates it against the context. A complex expression with multiple variables takes 1-5ms. Twelve thousand evaluations at 1ms each is 12 seconds of JavaScript execution blocking the UI thread.&lt;/p&gt;

&lt;p&gt;In practice, most expressions are simple (&lt;code&gt;= status = "closed"&lt;/code&gt;, &lt;code&gt;= amount &amp;gt; 1000&lt;/code&gt;) and &lt;code&gt;feelin&lt;/code&gt; is fast. The observed performance cost is more like 200-500ms for a 50-field form — noticeable but not catastrophic. But for forms with 100+ fields, or evaluators with expensive evaluation logic (like &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; which needs to parse date strings and compare Date objects), the cost accumulates.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; is the clearest case for scoped re-evaluation because datetime validation has a very narrow scope: it only matters when a datetime field changes. If the user types in a text field, no datetime validation needs to run.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Baseline Evaluator (Without Optimization)
&lt;/h2&gt;

&lt;p&gt;For comparison, here is what &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt; would look like following the standard pattern from Article 8, without any scoping:&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;// ❌ Unscoped — re-evaluates everything on every changed event&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DateTimeValidationEvaluator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_validationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form.init&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// ❌ Fires on EVERY field change — even text fields, dropdowns, checkboxes&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed&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="o"&gt;=&amp;gt;&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&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="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;data&lt;/span&gt; &lt;span class="o"&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;components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

      &lt;span class="c1"&gt;// ❌ Iterates ALL 50+ components to find datetime fields&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;datetimeComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Evaluate validation for each datetime component&lt;/span&gt;
      &lt;span class="c1"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 50-field form with 3 datetime fields, this evaluator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs 50 times during pre-population (once per field)&lt;/li&gt;
&lt;li&gt;Each run iterates all 50 components to find the 3 datetime ones&lt;/li&gt;
&lt;li&gt;Then evaluates validation expressions for those 3 fields&lt;/li&gt;
&lt;li&gt;Total: 50 × 50 iterations + 50 × 3 FEEL evaluations = 2,650 operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of those operations happen because text fields, dropdowns, and checkboxes changed — events that have nothing to do with datetime validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 1: Field-Type Gating
&lt;/h2&gt;

&lt;p&gt;The first optimization: check whether the changed field is a datetime field before doing anything:&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;// ✅ Field-type gating — skip evaluation when irrelevant fields change&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed&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;event&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&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="c1"&gt;// ✅ Extract what changed from the event&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&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;changedKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Check if any datetime field changed&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasDatetimeChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;changedKeys&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;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_isDatetimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Skip entirely if no datetime field changed&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasDatetimeChange&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&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 &lt;code&gt;_isDatetimeField&lt;/code&gt; check:&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="nf"&gt;_isDatetimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Check by schema ID (the component's id property)&lt;/span&gt;
  &lt;span class="c1"&gt;// Sometimes changed fires with the component's id, not its key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&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;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;fieldKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;fieldKey&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&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 check iterates all components to find one by key or ID. For a 50-component form this is fast — a simple array scan. The cost is much lower than running FEEL evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With field-type gating, the 50-field form pre-population becomes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 changed events fire during pre-population
  → 3 events: datetime field changed → evaluateAll() runs
  → 47 events: non-datetime fields changed → skip (early return)

Total: 3 evaluations instead of 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An 83% reduction in evaluation runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 2: Schema-Based Field Registry
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;_isDatetimeField&lt;/code&gt; check scans all components on every &lt;code&gt;changed&lt;/code&gt; event. For a large form, this adds up. Build the datetime field set once on &lt;code&gt;form.init&lt;/code&gt; instead:&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;// ✅ Build a Set of datetime field keys on init&lt;/span&gt;

&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;_buildDatetimeFieldRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&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;components&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;component&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Also add by id in case events use id instead of key&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;component&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ O(1) lookup instead of O(n) scan&lt;/span&gt;
&lt;span class="nf"&gt;_isDatetimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&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;Initialize on &lt;code&gt;form.init&lt;/code&gt; and rebuild when the schema changes:&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form.init&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_buildDatetimeFieldRegistry&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;propertiesPanel.updated&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Schema may have changed (new datetime field added or removed)&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_buildDatetimeFieldRegistry&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&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 &lt;code&gt;_isDatetimeField(key)&lt;/code&gt; is a Set lookup — O(1) regardless of form size.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 3: Context Filtering
&lt;/h2&gt;

&lt;p&gt;The standard evaluator pattern builds a context from all form data. &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt;'s FEEL expressions reference other datetime fields — &lt;code&gt;= value &amp;gt;= today()&lt;/code&gt;, &lt;code&gt;= startDate &amp;lt;= endDate&lt;/code&gt;. They never reference text fields, dropdowns, or checkboxes.&lt;/p&gt;

&lt;p&gt;Filtering the context to only datetime values makes evaluation faster and prevents irrelevant data from polluting the FEEL context:&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;// ❌ Full context — 50+ values, most irrelevant&lt;/span&gt;
&lt;span class="nf"&gt;prepareContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;key&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;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_coerceValue&lt;/span&gt;&lt;span class="p"&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;key&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Filtered context — only datetime values + date helpers&lt;/span&gt;
&lt;span class="nf"&gt;_getRelevantFormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="c1"&gt;// Only include datetime field values&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&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;fieldKey&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ✅ Convert string dates to Date objects for FEEL comparison&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;converted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_coerceDateValue&lt;/span&gt;&lt;span class="p"&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;fieldKey&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;converted&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;converted&lt;/span&gt; &lt;span class="p"&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;fieldKey&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="c1"&gt;// ✅ Current field's value available as 'value'&lt;/span&gt;
  &lt;span class="c1"&gt;// (set per-component during evaluation)&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Date/time helpers always available&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="mi"&gt;0&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="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="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tomorrow&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="mi"&gt;0&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="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="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yesterday&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="mi"&gt;0&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="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="nx"&gt;d&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;context&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;Why smaller context helps FEEL evaluation:&lt;/strong&gt; &lt;code&gt;feelin&lt;/code&gt; builds a lookup table for each variable name in the expression and resolves it against the context. A context with 50 keys has 50 lookups to manage. A context with 5 keys has 5. The difference is small per evaluation but adds up across thousands of evaluations.&lt;/p&gt;

&lt;p&gt;More importantly, a smaller context prevents accidental matches. If a text field has a key of &lt;code&gt;startDate&lt;/code&gt; and the FEEL expression references &lt;code&gt;startDate&lt;/code&gt;, a full-context evaluator would include the text field's string value. The filtered-context evaluator only includes values from actual datetime fields.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;_coerceDateValue&lt;/code&gt; Method
&lt;/h2&gt;

&lt;p&gt;DateTime validation evaluates expressions like &lt;code&gt;= value &amp;gt;= today()&lt;/code&gt;. For this comparison to work in FEEL, &lt;code&gt;value&lt;/code&gt; must be a &lt;code&gt;Date&lt;/code&gt; object, not the string &lt;code&gt;"2024-01-15"&lt;/code&gt; that's stored in form data.&lt;/p&gt;

&lt;p&gt;The coercion converts stored strings to &lt;code&gt;Date&lt;/code&gt; objects:&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="nf"&gt;_coerceDateValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// "2024-01-15" → Date at local midnight&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T00:00:00&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt; &lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// "2024-01-15 10:30:00" → Date (space to T)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// "10:30:00" → keep as string (time comparison is string comparison)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DateTimeValidationEvaluator] Could not coerce value: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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="kc"&gt;null&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 &lt;code&gt;T00:00:00&lt;/code&gt; suffix for date-only strings prevents the timezone offset problem from Article 17: &lt;code&gt;new Date('2024-01-15')&lt;/code&gt; parses as UTC midnight and shifts to the previous day in negative UTC offsets. &lt;code&gt;new Date('2024-01-15T00:00:00')&lt;/code&gt; parses as local midnight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 4: Change Detection on Results
&lt;/h2&gt;

&lt;p&gt;Even when evaluation runs, the results might be identical to the previous run. Writing identical errors to form state via &lt;code&gt;_setState&lt;/code&gt; triggers a re-render, which triggers another &lt;code&gt;changed&lt;/code&gt; event, which could trigger evaluation again.&lt;/p&gt;

&lt;p&gt;Track the last evaluated values and skip &lt;code&gt;_setState&lt;/code&gt; when nothing changed:&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;_lastEvaluatedValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;_lastValidationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;()&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;data&lt;/span&gt; &lt;span class="o"&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;allComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&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;datetimeComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_datetimeFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;datetimeComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="c1"&gt;// ✅ Get filtered context once — reuse for all components&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getRelevantFormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;newErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hasChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;datetimeComponents&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;component&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;rawValue&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;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="c1"&gt;// ✅ Check if this field's value changed since last evaluation&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastEvaluatedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Value hasn't changed — reuse previous error state&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastValidationErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;newErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastValidationErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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="c1"&gt;// Skip re-evaluation for this component&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// ✅ Value changed — run evaluation&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coercedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_coerceDateValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&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;componentContext&lt;/span&gt; &lt;span class="o"&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;baseContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;coercedValue&lt;/span&gt;   &lt;span class="c1"&gt;// 'value' always refers to this field's 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;componentErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_validateDatetimeComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;componentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;rawValue&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;componentErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;newErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;componentErrors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// ✅ Track what we evaluated&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastEvaluatedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastValidationErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;componentErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;componentErrors&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;span class="c1"&gt;// ✅ Check if the error state actually changed&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prevErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastValidationErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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;prevHadError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prevErrors&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;prevErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nowHasError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;componentErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="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;prevHadError&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;nowHasError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevErrors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;componentErrors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;hasChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="c1"&gt;// ✅ Only update form state if errors actually changed&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;hasChanges&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;currentErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;errors&lt;/span&gt; &lt;span class="o"&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;mergedErrors&lt;/span&gt; &lt;span class="o"&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;currentErrors&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// Remove old datetime errors and add new ones&lt;/span&gt;
      &lt;span class="nx"&gt;datetimeComponents&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;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;newErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;mergedErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;mergedErrors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mergedErrors&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_validationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newErrors&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[DateTimeValidationEvaluator] Error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; tracks:&lt;/strong&gt; For each datetime field, the last raw string value that was evaluated. If the current value matches the tracked value, evaluation is skipped and the previous error state is reused. Only fields whose values changed since the last evaluation run.&lt;/p&gt;

&lt;p&gt;On a 50-field form after pre-population completes, all 3 datetime fields have been evaluated and their values tracked. When the user types in a text field (triggering &lt;code&gt;changed&lt;/code&gt;), the field-type gate catches it first and skips the evaluator entirely. When the user changes a datetime field, only that field's value differs from &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; — the other two datetime fields are skipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Validation Logic
&lt;/h2&gt;

&lt;p&gt;The evaluation itself checks configured validation rules:&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="nf"&gt;_validateDatetimeComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawValue&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;errors&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Empty values are handled by RequiredValidator — not our responsibility&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Basic format validation first&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_validateDatetimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&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;formatError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formatError&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;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Don't run FEEL validation on invalid format&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Run configured FEEL validation rules&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationRules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;customValidations&lt;/span&gt; &lt;span class="o"&gt;||&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;rule&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;validationRules&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_evaluateFeel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&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;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;result&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`Validation failed for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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="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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`[DateTimeValidationEvaluator] Error evaluating rule for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_validateDatetimeFormat&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;type&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Invalid date format. Expected YYYY-MM-DD, got: &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="s2"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt; &lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Invalid datetime format. Expected YYYY-MM-DD HH:mm:ss`&lt;/span&gt;&lt;span class="p"&gt;;&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;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Invalid time format. Expected HH:mm:ss`&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Format is valid&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Format validation runs before FEEL validation. If the stored value is &lt;code&gt;"15-01-2024"&lt;/code&gt; (wrong format), there's no point running FEEL expressions that expect a parseable date — &lt;code&gt;_coerceDateValue&lt;/code&gt; would return &lt;code&gt;null&lt;/code&gt;, and &lt;code&gt;= value &amp;gt;= today()&lt;/code&gt; would evaluate to &lt;code&gt;null&lt;/code&gt; (not &lt;code&gt;false&lt;/code&gt;), producing no error when there should be one. Format validation catches the upstream problem first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measured Performance
&lt;/h2&gt;

&lt;p&gt;For a form with 50 fields (3 datetime fields) loading with pre-populated data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline (unscoped):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 &lt;code&gt;changed&lt;/code&gt; events on load&lt;/li&gt;
&lt;li&gt;50 evaluator runs&lt;/li&gt;
&lt;li&gt;Each run: iterate 50 components + evaluate 3 datetime fields&lt;/li&gt;
&lt;li&gt;Total operations: 50 × (50 + 3) = 2,650&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With field-type gating only:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 &lt;code&gt;changed&lt;/code&gt; events on load&lt;/li&gt;
&lt;li&gt;3 evaluator runs (only datetime field changes pass the gate)&lt;/li&gt;
&lt;li&gt;Each run: iterate 50 components + evaluate 3 datetime fields&lt;/li&gt;
&lt;li&gt;Total operations: 3 × (50 + 3) = 159&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With field-type gating + Set registry:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 evaluator runs&lt;/li&gt;
&lt;li&gt;Each run: 3 × O(1) Set lookups + evaluate 3 datetime fields&lt;/li&gt;
&lt;li&gt;Total operations: approximately 3 × (3 + 3) = 18&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With all optimizations (gating + Set + context filtering + change detection):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 evaluator runs&lt;/li&gt;
&lt;li&gt;First run evaluates all 3 datetime fields: 3 FEEL evaluations&lt;/li&gt;
&lt;li&gt;Subsequent runs for unchanged datetime fields: 0 FEEL evaluations (change detection skips)&lt;/li&gt;
&lt;li&gt;Total FEEL evaluations on load: 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reduction from 2,650 to ~18 operations is approximately &lt;strong&gt;99%&lt;/strong&gt; for the load phase. For a 100-field form with 5 datetime fields, the baseline would be 5,000+ operations; the optimized version stays at ~5.&lt;/p&gt;

&lt;p&gt;During active use (user filling the form), each datetime field change triggers at most 1 FEEL evaluation for the changed field and 0 for unchanged datetime fields. Text field changes trigger 0 evaluations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The General Pattern: Field-Type Gating Template
&lt;/h2&gt;

&lt;p&gt;Any evaluator that only cares about specific field types should implement these four optimizations. Here is the template:&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;// ScopedEvaluator.js — template for type-specific evaluators&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScopedEvaluator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ✅ Optimization 1 + 2: Type registry&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ✅ Optimization 4: Change detection&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastEvaluatedValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_feelEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_feelEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;evaluate&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ScopedEvaluator] FEEL engine unavailable&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="c1"&gt;// Initialize&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form.init&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_buildFieldRegistry&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// ✅ Optimization 1: Field-type gate on changed&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed&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;event&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changedKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&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;hasRelevantChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;changedKeys&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;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasRelevantChange&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="c1"&gt;// ✅ Skip — no relevant fields changed&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Rebuild registry when schema changes&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;propertiesPanel.updated&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_buildFieldRegistry&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Re-apply after render&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form.rendered&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_applyToDOM&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&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="c1"&gt;// ✅ Optimization 2: Build Set of relevant field keys&lt;/span&gt;
  &lt;span class="nf"&gt;_buildFieldRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&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;components&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;component&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;// ✅ Replace with your relevant types&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RELEVANT_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// example&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;RELEVANT_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&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;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;component&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;()&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;schema&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_form&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;data&lt;/span&gt; &lt;span class="o"&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;allComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

      &lt;span class="c1"&gt;// Only process relevant components&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relevantComponents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;relevantComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="c1"&gt;// ✅ Optimization 3: Build filtered context once&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getFilteredContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hasChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;relevantComponents&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;component&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;rawValue&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;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Optimization 4: Skip if value hasn't changed&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastEvaluatedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;rawValue&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="c1"&gt;// Nothing to do for this component&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Evaluate this component&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_evaluateComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if result changed&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastResults&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;hasChanges&lt;/span&gt; &lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastResults&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastEvaluatedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// ✅ Only apply if something changed&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;hasChanges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_applyResults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_applyToDOM&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ScopedEvaluator] Error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_evaluating&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// ✅ Optimization 3: Filter context to relevant fields only&lt;/span&gt;
  &lt;span class="nf"&gt;_getFilteredContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_relevantFieldKeys&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;fieldKey&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_coerceValue&lt;/span&gt;&lt;span class="p"&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;fieldKey&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="c1"&gt;// Add any helpers needed by your FEEL expressions&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHours&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="mi"&gt;0&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="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="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Implement these for your specific use case:&lt;/span&gt;

  &lt;span class="nf"&gt;_evaluateComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Your evaluation logic here&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_coerceValue&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Convert stored values to FEEL-compatible types&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_applyResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Write results to form state&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_applyToDOM&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Apply DOM changes if needed&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Standard utilities&lt;/span&gt;
  &lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&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;all&lt;/span&gt; &lt;span class="o"&gt;=&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;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getAllComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&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="nx"&gt;all&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;ScopedEvaluator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eventBus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&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 to Scope vs When to Evaluate Everything
&lt;/h2&gt;

&lt;p&gt;Not all evaluators benefit from scoping. The decision depends on two factors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Factor 1: What triggers the evaluator?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your evaluator only needs to run when fields of a specific type change, scope it. If it needs to run when &lt;em&gt;any&lt;/em&gt; field changes (like &lt;code&gt;DisabledEvaluator&lt;/code&gt; — any field change might affect whether another field should be disabled), don't scope it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Factor 2: How expensive is evaluation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If evaluation is cheap (simple boolean expression, no API calls), the overhead of scoping might exceed the savings. If evaluation is expensive (parsing date strings, comparing Date objects, running multiple rules per field), scoping pays off quickly.&lt;/p&gt;

&lt;p&gt;For the evaluators in this series:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluator&lt;/th&gt;
&lt;th&gt;Scope By Type?&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BindingEvaluator&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Any field can affect any computed binding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DisabledEvaluator&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Any field can affect any other field's disabled state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HideEvaluator&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Any field can affect any other field's visibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RequiredEvaluator&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Any field can affect any other field's required state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PersistentEvaluator&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Any field can affect persistent state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DateTimeValidationEvaluator&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only datetime changes affect datetime validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TicketAutoFillEvaluator&lt;/td&gt;
&lt;td&gt;Yes (watcher map)&lt;/td&gt;
&lt;td&gt;Only watched fields trigger auto-fill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;TicketAutoFillEvaluator&lt;/code&gt; uses the watcher map pattern (Article 20) rather than field-type gating — it's scoped by configuration rather than type. The result is the same: only relevant changes trigger evaluation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tradeoffs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; cache can go stale.&lt;/strong&gt; If a FEEL expression references external state — &lt;code&gt;context.today()&lt;/code&gt;, form data from other fields included via the base context — the expression result might change even though the field's raw value hasn't. The &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; guard would incorrectly skip re-evaluation.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;DateTimeValidationEvaluator&lt;/code&gt;, this is handled by the context filtering: the base context includes other datetime fields, and if those change, they pass the field-type gate and trigger a full &lt;code&gt;evaluateAll()&lt;/code&gt; run. The &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; guard then re-evaluates only the fields whose values changed.&lt;/p&gt;

&lt;p&gt;For evaluators where the context depends on fields outside the scoped set, the &lt;code&gt;_lastEvaluatedValues&lt;/code&gt; optimization is unsafe. In that case, use the field-type gate and context filtering but skip the per-value change detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Set registry must be rebuilt when the schema changes.&lt;/strong&gt; If the form designer adds a new datetime field while the editor is open, &lt;code&gt;_relevantFieldKeys&lt;/code&gt; won't include it until &lt;code&gt;_buildFieldRegistry()&lt;/code&gt; runs on the next &lt;code&gt;propertiesPanel.updated&lt;/code&gt; event. During the window between field addition and registry rebuild, the new field won't receive validation. The 50ms delay on &lt;code&gt;propertiesPanel.updated&lt;/code&gt; makes this window longer than necessary. Shortening it (or eliminating the delay) would reduce the window at the cost of potential redundant rebuilds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON.stringify for result comparison has edge cases.&lt;/strong&gt; For complex result objects (arrays with nested objects), &lt;code&gt;JSON.stringify&lt;/code&gt; can produce different strings for equivalent objects if key order differs. For the simple result types used in datetime validation (arrays of strings, booleans, null), this is not a problem in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;Scoped re-evaluation is the performance layer on top of the evaluator pattern. Combined with the caching in &lt;code&gt;TicketAutoFillEvaluator&lt;/code&gt; (Article 20), these two articles address the main performance concerns in a large-form extension system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Article 23&lt;/strong&gt; — the capstone — ties everything together: the complete system architecture, the full lifecycle from form instantiation to submission, and the three things I would do differently if starting over.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is Part 22 of "Extending bpmn-io Form-JS Beyond Its Limits." The series covers the complete architecture for production-grade Form-JS extensions — the documentation that doesn't exist yet.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;camunda&lt;/code&gt; &lt;code&gt;bpmn&lt;/code&gt; &lt;code&gt;formjs&lt;/code&gt; &lt;code&gt;performance&lt;/code&gt; &lt;code&gt;feel&lt;/code&gt; &lt;code&gt;optimization&lt;/code&gt; &lt;code&gt;runtime-evaluation&lt;/code&gt; &lt;code&gt;javascript&lt;/code&gt; &lt;code&gt;devex&lt;/code&gt;&lt;/p&gt;

</description>
      <category>camunda</category>
      <category>bpmnio</category>
      <category>formjs</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
