<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luciano0322</title>
    <description>The latest articles on DEV Community by Luciano0322 (@luciano0322).</description>
    <link>https://dev.to/luciano0322</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png</url>
      <title>DEV Community: Luciano0322</title>
      <link>https://dev.to/luciano0322</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luciano0322"/>
    <language>en</language>
    <item>
      <title>Query, Mutation, and Stream Are Not the Same Kind of Async Work</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Tue, 14 Jul 2026 05:25:42 +0000</pubDate>
      <link>https://dev.to/luciano0322/query-mutation-and-stream-are-not-the-same-kind-of-async-work-50ig</link>
      <guid>https://dev.to/luciano0322/query-mutation-and-stream-are-not-the-same-kind-of-async-work-50ig</guid>
      <description>&lt;p&gt;In the previous article, I discussed a common but dangerous oversimplification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Async work should not be reduced to &lt;code&gt;Promise.resolve()&lt;/code&gt; followed by &lt;code&gt;setState()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real problem is not that &lt;code&gt;setState()&lt;/code&gt; is inherently wrong. The problem is that we often treat every asynchronous result as if it were the same kind of thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API request completed       → write to state
Form submitted successfully → write to state
WebSocket message received  → write to state
Background job updated      → write to state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the UI's perspective, all of these eventually cause something on the screen to update. But from the perspective of data flow semantics, they are not the same kind of work.&lt;/p&gt;

&lt;p&gt;Some async work reads data.&lt;br&gt;
Some async work changes the state of a remote or local system.&lt;br&gt;
Some async work continuously observes changes over time.&lt;/p&gt;

&lt;p&gt;In other words, they should at least be separated into three different concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query&lt;/li&gt;
&lt;li&gt;Mutation&lt;/li&gt;
&lt;li&gt;Stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many frontend architectures eventually flatten all of them back into the same pipeline:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;async work resolved → update state → render again&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This path is familiar and convenient. It is also one of the major sources of state confusion. Because the difference between Query, Mutation, and Stream is not merely their API shape. They have different requirements around data ownership, lifecycle, caching, invalidation, cancellation, retries, and consistency.&lt;/p&gt;


&lt;h2&gt;
  
  
  Query: Reading a Data Fact
&lt;/h2&gt;

&lt;p&gt;A Query is often understood as something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this is only the execution form. It is not the semantic meaning.&lt;/p&gt;

&lt;p&gt;The essence of a Query is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to know the current state of a particular data fact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /users
GET /users/:id
GET /posts?authorId=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These operations mean that we are retrieving a piece of identifiable, cacheable, and revalidatable data. That is why one of the most important properties of a Query is identity: how this piece of data is identified within the system.&lt;/p&gt;

&lt;p&gt;For example:&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="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detail&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;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;byAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once a Query has identity, it should not merely be treated as temporary async logic inside a Component.&lt;/p&gt;

&lt;p&gt;The same Query result may be consumed by multiple places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a list page needs &lt;code&gt;users&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a detail page needs &lt;code&gt;user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a search box may need partial user data&lt;/li&gt;
&lt;li&gt;permission checks may depend on user data&lt;/li&gt;
&lt;li&gt;background synchronization may need to revalidate users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every place performs its own &lt;code&gt;fetch → setState&lt;/code&gt;, data ownership becomes confused. Eventually, you end up with multiple pieces of state that look like the same data, but have no semantic relationship with one another.&lt;/p&gt;

&lt;p&gt;A Query should not belong to the render lifecycle. It should be a Resource Node inside the Reactive Graph. It owns its identity, knows when it should reload, knows which sources it depends on, and manages its own loading and stale states.&lt;/p&gt;

&lt;p&gt;The responsibility of the Render Layer is simply to subscribe to and read the Snapshot of this Node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutation: Changing System State
&lt;/h2&gt;

&lt;p&gt;A Mutation often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateUser&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;patch&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;updatedUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real complexity of a Mutation is not sending the request.&lt;/p&gt;

&lt;p&gt;The hard part is what happens after the request succeeds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which data in the system is no longer trustworthy?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PATCH /users/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Mutation may affect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /users/:id
GET /users
GET /teams/:teamId/members
GET /audit-logs
GET /permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we only treat Mutation as “sending a request,” then after success, we usually end up with patterns like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;refetchUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;refetchUserDetail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&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;p&gt;These approaches can work. The problem is that the semantics are often scattered across the application. Component A knows it should refetch the list. Component B knows it should update the detail. Some hook knows it should invalidate the cache. Some effect adds an extra synchronization step.&lt;/p&gt;

&lt;p&gt;Over time, the system enters a difficult-to-maintain state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The impact of a Mutation is global, but the logic for handling its consequences is local.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the biggest difference between Mutation and Query.&lt;/p&gt;

&lt;p&gt;The core question of a Query is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which data do I want to read?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The core question of a Mutation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What did I change, and which existing data became invalid because of it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Mutation should not merely return data. It should also declare its impact on the data graph.&lt;/p&gt;

&lt;p&gt;Semantically, it should be closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutate&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;patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;invalidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detail&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;teamMembers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;byUser&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;invalidates&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;userResource&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;usersListResource&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 exact API shape is not the important part.&lt;/p&gt;

&lt;p&gt;The important part is the semantic model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Mutation should describe the invalidation relationships it creates, instead of pushing invalidation responsibility into the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a successful Mutation requires certain Queries to be revalidated, that should be the responsibility of the data layer, not a side effect inside some Component.&lt;/p&gt;

&lt;p&gt;A Component is not the owner of the data graph. A Component only knows what it wants to display. It should not be responsible for understanding which nodes in the entire data world need to be recalculated after a Mutation changes something.&lt;/p&gt;

&lt;p&gt;The better abstraction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mutation changes the data graph
        ↓
related nodes become invalid
        ↓
Reactive Graph propagates the change
        ↓
UI updates passively
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stream: Not a Continuous Query, but a Long-Lived Data Source
&lt;/h2&gt;

&lt;p&gt;Streams deal with things like WebSocket, Server-Sent Events, AI token streaming, or real-time collaborative updates.&lt;/p&gt;

&lt;p&gt;Many times, we simplify Stream handling into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&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;prev&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="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;Or:&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="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunk&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 fine for small demos, but it quickly exposes another semantic mismatch.&lt;/p&gt;

&lt;p&gt;A Stream is a long-lived data relationship.&lt;br&gt;
A Query has a beginning and an end.&lt;br&gt;
A Mutation has submission and completion.&lt;br&gt;
A Stream continuously pushes data over time.&lt;/p&gt;

&lt;p&gt;Its concerns are not limited to &lt;code&gt;loading&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It also needs to deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection health and reconnection&lt;/li&gt;
&lt;li&gt;the distinction between Current Value and Stable Value&lt;/li&gt;
&lt;li&gt;backpressure&lt;/li&gt;
&lt;li&gt;subscription lifecycle&lt;/li&gt;
&lt;li&gt;whether multiple consumers should share the same Stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These concerns cannot be properly described by a normal &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, consider AI token streaming:&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="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;token&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&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 the surface, this is just appending text.&lt;/p&gt;

&lt;p&gt;But semantically, there are at least three different states:&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="nx"&gt;currentValue&lt;/span&gt;   &lt;span class="c1"&gt;// the value currently flowing&lt;/span&gt;
&lt;span class="nx"&gt;stableValue&lt;/span&gt;    &lt;span class="c1"&gt;// the completed value that can be treated as stable&lt;/span&gt;
&lt;span class="nx"&gt;streamStatus&lt;/span&gt;   &lt;span class="c1"&gt;// streaming / completed / cancelled / error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we use a single state value to represent all of these, they are easily mixed together. The UI may need to display tokens in real time. The cache may only want to store the completed result. Derived State may not want to recompute on every token. Effects may only need to run after the stream is completed.&lt;/p&gt;

&lt;p&gt;This is the biggest difference between Stream and Query. A Query result can be treated as a snapshot. A Stream result is a trajectory of changes over time. It should be modeled as a long-lived Async Resource, not as an endless &lt;code&gt;setState&lt;/code&gt; loop inside a Component.&lt;/p&gt;




&lt;h2&gt;
  
  
  When All Three Are Mixed Together, the System Loses Its Boundaries
&lt;/h2&gt;

&lt;p&gt;If Query, Mutation, and Stream are all flattened into the same process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async work → setState → render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the system gradually loses its Async Boundary.&lt;/p&gt;

&lt;p&gt;You eventually start asking questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this loading state for a Query, or is it a Mutation pending state?&lt;/li&gt;
&lt;li&gt;Is this error caused by data fetching, or by submission failure?&lt;/li&gt;
&lt;li&gt;Is this data a stable result, or an intermediate Stream value?&lt;/li&gt;
&lt;li&gt;Who triggered this refetch?&lt;/li&gt;
&lt;li&gt;Is this effect synchronizing Derived State, or patching up invalidation after a Mutation?&lt;/li&gt;
&lt;li&gt;Is this state the source of truth, or just a snapshot of an async result?&lt;/li&gt;
&lt;li&gt;Why does this Component know so many data invalidation rules?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions are not obvious at first. In small applications, the differences between Query, Mutation, and Stream can still be remembered by humans. But as the system grows, relying on human memory to maintain semantic boundaries eventually fails.&lt;/p&gt;

&lt;p&gt;What remains is a pile of code that looks reasonable, but is semantically confused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;refetch&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;submitted&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setLocalUser&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;success&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;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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamDone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;saveToCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;streamDone&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutationSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;reloadList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;reloadDetail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;reloadPermissions&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;mutationSuccess&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These examples are not necessarily wrong. But they reveal something important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The async semantics of the system are hidden inside the UI lifecycle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once async semantics are hidden inside Components, it becomes difficult to understand where the real ownership of the data flow lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Render Should Not Be Responsible for Understanding Async Semantics
&lt;/h2&gt;

&lt;p&gt;The responsibility of Render should be simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Describe what the UI looks like under the current Snapshot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the responsibility of Query, Mutation, and Stream is different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Describe how async data exists, changes, becomes invalid, stabilizes, and is consumed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These two responsibilities should not be mixed.&lt;/p&gt;

&lt;p&gt;When Render needs to know too many details about async events, the architectural boundary has already started to shift in the wrong direction.&lt;/p&gt;

&lt;p&gt;For example, a Component may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUsersQuery&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;updateUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUpdateUserMutation&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;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserActivityStream&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refetch&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;updateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refetch&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;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of code is very common in frontend applications.&lt;/p&gt;

&lt;p&gt;But from the perspective of ownership, it reveals something deeper:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Component is patching holes in the data graph.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is no longer just rendering. It knows which Queries should be updated after a Mutation succeeds. It knows which data should be refreshed after a Stream completes. It knows causal relationships between async states.&lt;/p&gt;

&lt;p&gt;Once this knowledge is scattered across Components, the system becomes increasingly difficult to reason about. The rules of data flow are no longer centralized in the data layer. They are hidden across hooks, effects, callbacks, and lifecycles.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Model: Return Async Work to the Reactive Graph
&lt;/h2&gt;

&lt;p&gt;If we model Query, Mutation, and Stream as different kinds of nodes in the Reactive Graph, the responsibilities become much clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query   (resource)       → readable, cacheable, invalidatable data node
Mutation (mutation)      → operation node that changes system state and declares invalidation relationships
Stream  (streamResource) → long-lived node for time-series data, with current value and stable value separated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under this model, the UI only consumes Snapshots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useResource&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streamView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStreamResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data graph itself is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancelling obsolete Queries when Sources change&lt;/li&gt;
&lt;li&gt;invalidating related Queries after Mutations succeed&lt;/li&gt;
&lt;li&gt;updating Current Value while a Stream is flowing&lt;/li&gt;
&lt;li&gt;committing Stable Value when a Stream completes&lt;/li&gt;
&lt;li&gt;making Derived State depend only on the nodes it actually needs&lt;/li&gt;
&lt;li&gt;executing Effects only at semantically correct moments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not about making simple things complicated. It is about giving each kind of async work the correct semantic home. If we do not build the abstraction here, the complexity does not disappear. It simply flows into UI Components as &lt;code&gt;useEffect&lt;/code&gt;. This is also why understanding async work purely as a request wrapper is usually not enough.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, &lt;code&gt;retry&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt; are all important. But they solve problems at the request execution level.&lt;/p&gt;

&lt;p&gt;The harder question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After the request completes, what does it mean inside the data graph?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Conclusion: Async Data Flow Needs Semantics, Not Just State
&lt;/h2&gt;

&lt;p&gt;Query, Mutation, and Stream all affect the UI. But they are not the same kind of async work.&lt;/p&gt;

&lt;p&gt;If we only look at them from the perspective of Render, they all look 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;something changed → update UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But from the perspective of system architecture, they are three fundamentally different behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query    → Read a fact
Mutation → Change the world
Stream   → Observe a timeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three behaviors require different ownership models. They also require different runtime semantics.&lt;/p&gt;

&lt;p&gt;Only by separating the semantics of Query, Mutation, and Stream can frontend architecture avoid forcing the UI lifecycle to carry the burden of the entire async world.&lt;/p&gt;

&lt;p&gt;This is also the core philosophy behind how I designed the async runtime in &lt;code&gt;signal-kernel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The goal is not only to make async work easier to trigger UI updates. The goal is to give async work the correct semantic position within the system architecture. And this becomes even more important once AI Agents begin participating in software development.&lt;/p&gt;

&lt;p&gt;If Query, Mutation, and Stream are scattered across Components, hooks, effects, and callbacks, even humans will struggle to reason about their boundaries. An AI Agent has even less chance of understanding the true semantics of the data flow.&lt;/p&gt;

&lt;p&gt;That is the topic I want to explore in the next article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When AI Agents can generate more and more code, does your system have clear enough architectural boundaries for both humans and Agents to reason about it correctly?&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Decoupling Async State from UI Lifecycles</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:45:59 +0000</pubDate>
      <link>https://dev.to/luciano0322/decoupling-async-state-from-ui-lifecycles-pac</link>
      <guid>https://dev.to/luciano0322/decoupling-async-state-from-ui-lifecycles-pac</guid>
      <description>&lt;p&gt;In my previous articles, I’ve consistently emphasized a core architectural principle: &lt;strong&gt;once the render layer no longer dictates the entire data flow, the boundaries between State, Derived State, and Effects become critical.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When we fall into the habit of stuffing every UI-affecting variable into generic "state," the system quickly loses its semantic structure. In modern frontend applications, this architectural gap becomes most glaring when dealing with asynchronous work.&lt;/p&gt;

&lt;p&gt;Async data is never merely "a value that will appear in the future." It carries complex semantics regarding its source, temporal validity, cancellation, error recovery, and invalidation. If these semantics aren't modeled explicitly, they inevitably get pushed down into the UI framework’s lifecycle—indirectly patched together through component mounts, effect dependencies, and callback guards.&lt;/p&gt;

&lt;p&gt;This brings us to the core question of this article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does a system lose when the correctness of async work is forced to depend on the UI lifecycle?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We are all incredibly familiar with this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchSomething&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, using a standard UI framework hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="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;let&lt;/span&gt; &lt;span class="nx"&gt;cancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nf"&gt;fetchSomething&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;result&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="nx"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setData&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cancelled&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing inherently wrong with this code for simple use cases. It’s intuitive and perfectly aligns with how Promises are designed to work: trigger the operation, wait for the resolution, and write the result back into state.&lt;/p&gt;

&lt;p&gt;However, this mental model has a subtle downside. It encourages us to think of async work as simply calling &lt;code&gt;setState&lt;/code&gt; after a Promise resolves. That may hold up for simple screens, but as an application grows, the model starts to expose structural problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Promise Only Describes Completion, Not Ownership
&lt;/h2&gt;

&lt;p&gt;A Promise solves a very specific problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A piece of work will complete in the future, and it will either succeed or fail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can perfectly describe the transition from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending → fulfilled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending → rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a Promise itself does not answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns this data?&lt;/li&gt;
&lt;li&gt;Is the data still valid when the Promise resolves?&lt;/li&gt;
&lt;li&gt;How does this result cascade to other derived data nodes?&lt;/li&gt;
&lt;li&gt;If an error occurs, who owns the recovery state?&lt;/li&gt;
&lt;li&gt;During a refetch, should we discard the old data or implement a stale-while-revalidate pattern?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Promise describes the lifecycle of an execution, but it does not define where that execution belongs within the application’s broader data flow. The true challenge in frontend engineering isn't waiting for a Promise to finish; it's orchestrating &lt;strong&gt;how that result correctly enters the system.&lt;/strong&gt; If we just shove the payload into component state, we are making the UI lifecycle responsible for data coordination that it was never designed to model.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;setState&lt;/code&gt; Is Not the Boundary of Async Correctness
&lt;/h2&gt;

&lt;p&gt;The responsibility of &lt;code&gt;setState&lt;/code&gt; is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Write a value into a state container and notify the UI that it may need to re-render.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But async correctness involves far more than assigning a value.&lt;/p&gt;

&lt;p&gt;Take the classic race condition example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&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;userId&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens if &lt;code&gt;userId&lt;/code&gt; changes while the request is still in flight?&lt;/p&gt;

&lt;p&gt;Imagine this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetchUser(1) starts
fetchUser(2) starts
fetchUser(2) finishes first
fetchUser(1) finishes later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we simply call &lt;code&gt;setState&lt;/code&gt; after each Promise resolves, the UI may end up showing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userId = 2
user data = user 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a flaw in the Promise—it's faithfully delivering the payload. The real problem is that the system lacks a clear semantic layer for deciding whether this async result still belongs to the current data state.&lt;/p&gt;

&lt;p&gt;So we start adding guards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadUser&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="kr"&gt;string&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;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;current&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&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;user&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;Or we use cancellation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&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;AbortController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These techniques are practical, and they do solve local problems.&lt;/p&gt;

&lt;p&gt;But fundamentally, they are patching the same issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Async work has not been placed inside the semantics of the data flow. It is treated as an external process that pushes a result back into state after completion.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Loading, Error, and Data Are Not Independent States
&lt;/h2&gt;

&lt;p&gt;Another common pattern is to split async work into several pieces of state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&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;false&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;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we manually synchronize them during the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;setError&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="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;await&lt;/span&gt; &lt;span class="nf"&gt;fetchSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;setData&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="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="nf"&gt;setError&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="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the UI perspective, splitting async work into &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt; feels reasonable. The UI does need to render different things based on different async states. But semantically, these three values are not unrelated pieces of state. They describe different aspects of the same &lt;strong&gt;Async Resource&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt; should not just be scattered fields inside a component. They should be understood as different projections of the same resource state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Otherwise, the system can easily enter inconsistent combinations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loading = false
error = null
data = stale data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loading = true
error = previous error
data = previous data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loading = false
error = error
data = new data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some combinations are valid. Some are bugs. Some depend on product semantics.&lt;/p&gt;

&lt;p&gt;For example, when reloading data, should the previous data be preserved?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = loading
data = previous data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be perfectly reasonable because the UI may want to display stale-while-revalidate behavior.&lt;/p&gt;

&lt;p&gt;But if these values are just split across multiple &lt;code&gt;setState&lt;/code&gt; calls, the system itself does not know which combinations are valid. The developer has to manually preserve these relationships in every component.&lt;/p&gt;




&lt;h2&gt;
  
  
  Effects Should Not Coordinate Every Async Task
&lt;/h2&gt;

&lt;p&gt;In many frontend architectures, Effects are treated as the universal solution for async work. This is understandable. API requests, WebSockets, and timers are all interactions with the external world.&lt;/p&gt;

&lt;p&gt;However, when Effects take on too much async coordination, they become the center of system complexity.&lt;/p&gt;

&lt;p&gt;It is easy to end up with logic like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Effect A fetches data&lt;/li&gt;
&lt;li&gt;Effect B listens to data and updates derived state&lt;/li&gt;
&lt;li&gt;Effect C writes derived state into cache&lt;/li&gt;
&lt;li&gt;Effect D listens to mutation results and triggers refetch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the surface, this looks reactive. But in reality, it has degraded into a process network maintained by execution order.&lt;/p&gt;

&lt;p&gt;Correctness no longer depends on clear dependencies. It depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Effect runs first?&lt;/li&gt;
&lt;li&gt;Which state is cleared first?&lt;/li&gt;
&lt;li&gt;Which callback observes which intermediate value?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nature of an Effect is to &lt;strong&gt;execute a side effect&lt;/strong&gt;, not to &lt;strong&gt;express a data relationship&lt;/strong&gt;. If starting requests, cancelling requests, checking validity, synchronizing status, and updating cache are all pushed into Effects, the problem is not merely that the code becomes longer.&lt;/p&gt;

&lt;p&gt;The real problem is that the semantic boundaries of the architecture have collapsed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async Results Carry Temporal Semantics
&lt;/h2&gt;

&lt;p&gt;Synchronous state changes are relatively intuitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
count = 2
count = 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But async results are different. An async result carries time semantics. It is not merely: &lt;code&gt;value = result&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It also implies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which request produced this result?&lt;/li&gt;
&lt;li&gt;Is this result later than the current state?&lt;/li&gt;
&lt;li&gt;Does this result still correspond to the current source?&lt;/li&gt;
&lt;li&gt;Has this result already become stale?&lt;/li&gt;
&lt;li&gt;Should this result overwrite the existing data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So async state is not ordinary state. It is data state with source, time, and validity semantics. If we simplify it into “call &lt;code&gt;setState&lt;/code&gt; after the Promise resolves,” we are hiding those temporal semantics.&lt;/p&gt;

&lt;p&gt;In the short term, the code may look simpler. But in the long term, the complexity does not disappear. It only moves somewhere harder to trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  From “The Request Completed” to “The Resource State Changed”
&lt;/h2&gt;

&lt;p&gt;The mindset shift is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not only care whether an async function has completed.&lt;br&gt;
Care about how the state of an async resource changes as its dependencies change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, we move from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch completes → setState
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source changes → resource re-evaluates
resource state changes → dependent nodes in the graph update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The former is an imperative process. The latter is a data flow model.&lt;/p&gt;

&lt;p&gt;In the former model, the developer has to manually decide every step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When should we fetch?&lt;/li&gt;
&lt;li&gt;When should loading be set?&lt;/li&gt;
&lt;li&gt;When should data be set?&lt;/li&gt;
&lt;li&gt;When should error be set?&lt;/li&gt;
&lt;li&gt;When should a stale result be ignored?&lt;/li&gt;
&lt;li&gt;When should refetch happen?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the latter model, these concerns can be wrapped into the semantics of a Resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;value&lt;/li&gt;
&lt;li&gt;status&lt;/li&gt;
&lt;li&gt;error&lt;/li&gt;
&lt;li&gt;reload&lt;/li&gt;
&lt;li&gt;cancel&lt;/li&gt;
&lt;li&gt;invalidate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean developers should lose control over async work. Quite the opposite. A good async model should make control more explicit.&lt;/p&gt;

&lt;p&gt;But that control should not be scattered across render functions and effect chains. It should be concentrated at the semantic boundary of the Resource.&lt;/p&gt;




&lt;h2&gt;
  
  
  Treating Async Work as a Data Node in the Reactive Graph
&lt;/h2&gt;

&lt;p&gt;When I first started building &lt;a href="https://github.com/Luciano0322/signal-kernel" rel="noopener noreferrer"&gt;&lt;code&gt;signal-kernel&lt;/code&gt;&lt;/a&gt;, my focus was on basic reactive primitives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signal&lt;/li&gt;
&lt;li&gt;Computed&lt;/li&gt;
&lt;li&gt;Effect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when I began working on Async Resources, I realized that async work is directly related to the correctness of the entire reactive graph.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In real applications, data is rarely produced only synchronously.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Data often comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API requests&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;Streams&lt;/li&gt;
&lt;li&gt;Background tasks&lt;/li&gt;
&lt;li&gt;User actions&lt;/li&gt;
&lt;li&gt;Mutation responses&lt;/li&gt;
&lt;li&gt;Cache hydration&lt;/li&gt;
&lt;li&gt;Server snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all async work can only complete outside the graph and then push its result back through &lt;code&gt;setState&lt;/code&gt;, the reactive graph can only guarantee so much. It can guarantee updates for synchronous dependencies.&lt;/p&gt;

&lt;p&gt;But it can't fully handle the semantics introduced by async work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;time&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;invalidation&lt;/li&gt;
&lt;li&gt;reload&lt;/li&gt;
&lt;li&gt;errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the purpose of &lt;code&gt;async-runtime&lt;/code&gt; is not to wrap Promise in a nicer API.&lt;/p&gt;

&lt;p&gt;Its core idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Async work should become a trackable, cancellable, invalidatable, observable data node inside the reactive graph.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is also why I increasingly do not see Async Resource as merely a UI data-fetching problem. It is an extension of data flow ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI Should Not Be the Only Owner of Async Correctness
&lt;/h2&gt;

&lt;p&gt;In traditional frontend thinking, async work is often tied to the component lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch when the component mounts&lt;/li&gt;
&lt;li&gt;Cancel when the component unmounts&lt;/li&gt;
&lt;li&gt;Refetch when dependencies change&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;setState&lt;/code&gt; when the Promise resolves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simple screens, this is intuitive and completely reasonable. The problem appears when the scope of async data is much larger than a single component.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User information&lt;/li&gt;
&lt;li&gt;Permission data&lt;/li&gt;
&lt;li&gt;Shopping cart state&lt;/li&gt;
&lt;li&gt;Notification state&lt;/li&gt;
&lt;li&gt;Background synchronization&lt;/li&gt;
&lt;li&gt;Mutation results&lt;/li&gt;
&lt;li&gt;Cache hydration&lt;/li&gt;
&lt;li&gt;AI agent execution state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These pieces of data may be consumed by multiple screens, multiple components, multiple derived states, and multiple effects at the same time.&lt;/p&gt;

&lt;p&gt;If their lifecycle is fully attached to a single component, the system starts to become rigid and inconsistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component unmounts, but the data still needs to be preserved.&lt;/li&gt;
&lt;li&gt;The component mounts again, and the same data is requested again.&lt;/li&gt;
&lt;li&gt;A mutation succeeds, but the system does not know which resources should be invalidated.&lt;/li&gt;
&lt;li&gt;One screen has updated, but another screen is still showing stale data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the point is not that UI should never use async state.&lt;/p&gt;

&lt;p&gt;The point is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;UI should not be the only owner of async correctness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Promise + setState&lt;/code&gt; is a perfectly valid implementation detail for one-off forms, local modals, and short-lived component state. But if it becomes the async architecture of the entire application, the system quickly loses its boundaries.&lt;/p&gt;

&lt;p&gt;Only when async work is formally brought into the semantic boundary of the reactive graph can the system pull async correctness back from scattered control flow and restore it as an understandable, traceable, and maintainable data structure.&lt;/p&gt;

&lt;p&gt;In the next article, I will discuss another problem that is even easier to confuse:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query, Mutation, and Stream are not the same kind of async work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They all look related to async operations, but their semantics in the data flow are fundamentally different. If we treat all of them as merely “a Promise,” the system will quickly lose its boundaries again.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>A lot of frontend bugs do not start from bad code.
They start from a small semantic mistake:
Treating Derived State as if it were real State.
Full article is here👇</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Tue, 30 Jun 2026 12:47:10 +0000</pubDate>
      <link>https://dev.to/luciano0322/a-lot-of-frontend-bugs-do-not-start-from-bad-code-they-start-from-a-small-semantic-mistake-1d5m</link>
      <guid>https://dev.to/luciano0322/a-lot-of-frontend-bugs-do-not-start-from-bad-code-they-start-from-a-small-semantic-mistake-1d5m</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i" class="crayons-story__hidden-navigation-link"&gt;Deconstructing the Semantic Confusion Between State, Derived State, and Effects&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/luciano0322" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" alt="luciano0322 profile" class="crayons-avatar__image" width="256" height="256"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/luciano0322" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Luciano0322
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Luciano0322
                
              
              &lt;div id="story-author-preview-content-4016017" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/luciano0322" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" class="crayons-avatar__image" alt="" width="256" height="256"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Luciano0322&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i" id="article-link-4016017"&gt;
          Deconstructing the Semantic Confusion Between State, Derived State, and Effects
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deconstructing the Semantic Confusion Between State, Derived State, and Effects</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 29 Jun 2026 09:28:29 +0000</pubDate>
      <link>https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i</link>
      <guid>https://dev.to/luciano0322/deconstructing-the-semantic-confusion-between-state-derived-state-and-effects-31i</guid>
      <description>&lt;p&gt;In modern frontend architecture, the word “state” has gradually become overgeneralized.&lt;/p&gt;

&lt;p&gt;API payloads, user input, computed frontend values, request loading status, and even the result of a side effect are often all placed under the broad category of “state.” At the UI level, this seems reasonable—anything that triggers a UI update looks like state. And once something is considered state, we tend to put it into a component, a store, a ref, a reactive object, or whatever state management tool is at our disposal.&lt;/p&gt;

&lt;p&gt;But this is exactly where the problem begins. When the semantics of State, Derived State, and Effects are blurred together, the boundaries of the system’s data flow gradually start to collapse.&lt;/p&gt;




&lt;h2&gt;
  
  
  State is Not a Catch-All Term
&lt;/h2&gt;

&lt;p&gt;From an architectural perspective, I prefer to think of State as the &lt;strong&gt;source of truth&lt;/strong&gt; within a data flow.&lt;/p&gt;

&lt;p&gt;It is the starting point from which subsequent derivations are produced. State should not be a value computed from something else, a temporary cache produced by a side effect, or a duplicated copy created merely for rendering convenience. It is the true entry point of data into the system. It can be changed by external events, user actions, or asynchronous results, driving the rest of the data flow forward.&lt;/p&gt;

&lt;p&gt;For example:&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;// This is real State: the source node in a data flow&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the following case is different:&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;// This is Derived State: it depends on count and shouldn't own an independent lifecycle&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;double&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;count&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;double&lt;/code&gt; is also data, and it may need to be rendered. But semantically, it is Derived State. Its existence depends entirely on &lt;code&gt;count&lt;/code&gt;. It lacks an independent data source, so treating it as independently owned State introduces synchronization cost and the risk of Data Drift. Once we promote Derived State into normal State, the cost of synchronization and the risk of &lt;strong&gt;Data Drift&lt;/strong&gt; immediately follow.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem of Derived State: Misplaced Ownership
&lt;/h2&gt;

&lt;p&gt;Almost every complex application requires Derived State—subtotals, form validation statuses, filtered lists, and so on. The computation itself isn't the issue. The real architectural question is: &lt;strong&gt;Who owns this Derived State?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Derived State is written into Component State, it becomes a "fake source of truth" that must be manually synchronized. Consider this common React pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUsers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveUsers&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="c1"&gt;// Synchronize data through lifecycle and Effect&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;setActiveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code splits a simple derivation into three disjointed parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The source&lt;/li&gt;
&lt;li&gt;The duplicated copy&lt;/li&gt;
&lt;li&gt;The synchronization logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where system boundaries blur. We are forced to ask a series of architectural questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is &lt;code&gt;activeUsers&lt;/code&gt; a source or a derived result?&lt;/li&gt;
&lt;li&gt;What state is the system in during that tiny window after &lt;code&gt;users&lt;/code&gt; has changed but before the Effect has run?&lt;/li&gt;
&lt;li&gt;If the Effect is skipped due to a flawed dependency array, will the data become permanently out of sync?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When data correctness is no longer guaranteed by structural relationships, but instead relies on execution timing, the maintenance cost of the system skyrockets.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Danger of Effects: The Illusion of Omnipotence
&lt;/h2&gt;

&lt;p&gt;Effects are arguably the most easily abused mechanism in frontend development simply because they are &lt;em&gt;convenient&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An Effect can sync data, call an API, subscribe to events, write to &lt;code&gt;localStorage&lt;/code&gt;, manipulate the DOM, or write Derived State back into State. Because Effects can do almost anything, complex data-flow problems often end up being pushed into them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Looks like Derived State:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;expensiveCalculation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Looks like Async Work:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;fetchData&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;setData&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="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;ul&gt;
&lt;li&gt;Closer to an actual external Side Effect:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data_loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real-world projects, these three distinct semantics are routinely crammed into the exact same mechanism. As a result, the Effect gradually devolves into a universal escape hatch.&lt;/p&gt;

&lt;p&gt;Hard to express a data relationship? &lt;em&gt;Use an Effect&lt;/em&gt;. Need to keep two states in sync? &lt;em&gt;Use an Effect&lt;/em&gt;. Don't know where a workflow belongs? &lt;em&gt;Just throw it in an Effect.&lt;/em&gt; In the short term, it's convenient. In the long term, it destroys your data-flow boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Sync Logic Becomes an Effect, You Rely on Execution Order
&lt;/h2&gt;

&lt;p&gt;The relationship between State and Derived State should be structural. Once the source data is known, the derived result should be deterministic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source state -&amp;gt; derived state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But when Derived State is synchronized via an Effect, correctness no longer relies solely on dependencies; it relies on whether the Effect executes at the right time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source state -&amp;gt; render -&amp;gt; effect -&amp;gt; set derived state -&amp;gt; render again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This path introduces unnecessary uncertainty. Effects are usually attached to a lifecycle or rendering process &lt;em&gt;after the fact&lt;/em&gt;. This means updates might be delayed until after the render, fail due to stale closures, or clash with interleaved Effects.&lt;/p&gt;

&lt;p&gt;This is one of the root causes of many async bugs and inconsistent data states. The issue isn't careless developers; it's a system forcing distinct data semantics into a single execution model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Restoring Semantic Roles
&lt;/h2&gt;

&lt;p&gt;I prefer to separate these concepts into three distinct roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State:&lt;/strong&gt; The source data of the system. It can be changed by external events or user actions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Derived State:&lt;/strong&gt; Data derived from State or other Derived State. It should not be manually synchronized.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Effect:&lt;/strong&gt; An action that produces impact outside the data graph after data changes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A healthier data flow should look more 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;State
  ↓
Derived State
  ↓
Effect / Render / Async Boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the data graph is established, it becomes obvious that not everything needs to serve the Render cycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Components Shouldn't Bear the Weight of All Data Semantics
&lt;/h2&gt;

&lt;p&gt;Frontend architecture becomes heavy because Components are forced to do too much. They describe UI, but they're also expected to manage Local State, compute Derived State, fire Async Requests, execute Effects, and manually juggle &lt;code&gt;useMemo&lt;/code&gt; to prevent rendering bottlenecks.&lt;/p&gt;

&lt;p&gt;When we use lifecycles to patch data flow, it's a glaring symptom that the system lacks distinct data boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Is Also Why I Started Rethinking &lt;code&gt;signal-kernel&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Initially, I thought I was just building a fine-grained reactive system—providing primitives like Signal, Computed, and Effect for precise updates. But its true architectural value isn't just "more granular updates."&lt;/p&gt;

&lt;p&gt;The deeper value is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It establishes a data-flow model based on a Reactive Graph, making semantic boundaries clear again.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this model, business logic and data flow are no longer owned by the UI framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal&lt;/strong&gt; holds Source State.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computed&lt;/strong&gt; holds Derived State, ensuring derivations are automatically tracked, not manually synced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effect&lt;/strong&gt; handles actual side effects (I/O, DOM manipulation).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this Reactive Graph is established, &lt;strong&gt;Render is just another consumer&lt;/strong&gt;. The data flow is structured inside the graph first, and the UI simply reflects the current slice of that graph.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let Data Semantics Return to Their Proper Place
&lt;/h2&gt;

&lt;p&gt;As frontend apps grow, state increases naturally. More state isn't the enemy—semantic confusion is.&lt;/p&gt;

&lt;p&gt;When Source State, Derived State, Effect Results, Async Statuses, and Render Caches are all treated as the exact same thing, boundaries vanish. You lose track of the true source. You rely on memoization to hide unclear ownership.&lt;/p&gt;

&lt;p&gt;The true challenge of frontend architecture isn't just updating the screen efficiently; it's giving every piece of data a clear semantic owner. When these boundaries are restored, the system evolves from "patching data through lifecycles" to being "driven by data relationships."&lt;/p&gt;

&lt;p&gt;This is the true value of a Reactive Graph. It makes the invisible boundaries of data visible again.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>When Render No Longer Owns the Entire Data Flow</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 22 Jun 2026 09:16:01 +0000</pubDate>
      <link>https://dev.to/luciano0322/when-render-no-longer-owns-the-entire-data-flow-20co</link>
      <guid>https://dev.to/luciano0322/when-render-no-longer-owns-the-entire-data-flow-20co</guid>
      <description>&lt;p&gt;In frontend development, it is very easy to think of data flow and rendering as the same thing. After all, for most applications, the final goal is to display data on the screen. Data comes back from an API, gets written into state, the component re-renders, and the UI updates.&lt;/p&gt;

&lt;p&gt;This path is so familiar that we often unconsciously assume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The purpose of data changes is to trigger render.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as an application scales, this assumption begins to crack. Data flow does not exist exclusively to serve the UI. In a complex system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some data changes simply update derived state.&lt;/li&gt;
&lt;li&gt;Some trigger background tasks.&lt;/li&gt;
&lt;li&gt;Some synchronize local caches.&lt;/li&gt;
&lt;li&gt;Some orchestrate async resources.&lt;/li&gt;
&lt;li&gt;Some happen entirely under the hood, with no need for the UI to ever know.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When every piece of data is forced to route back through a component—tangled up in render lifecycles, hooks, watchers, or effects—the architecture inevitably turns into a mess. This isn’t a flaw in React or Vue. It happens because, fundamentally, render was never meant to own the entire data flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Render Is a Consumer of Data Flow, Not the Owner
&lt;/h2&gt;

&lt;p&gt;Over time, I've come to deeply embrace this mindset:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Render is just one side effect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence deeply changed the way I think about frontend architecture, because it reframes the core problem.&lt;/p&gt;

&lt;p&gt;Render is, of course, extremely important. Without render, there is no visible output for the user. But from the perspective of a reactive system, render should never be the center of the data flow. It is only one possible side effect produced by data changes.&lt;/p&gt;

&lt;p&gt;When a piece of data changes, the system may trigger a chain of reactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source state changes
        ↓
derived state updates
        ↓
effects run
        ↓
render may happen
        ↓
async work may continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render is one result of the system.&lt;br&gt;
It is not the owner of the system.&lt;/p&gt;

&lt;p&gt;However, many frontend architectures and usage patterns make us unconsciously treat render as the ownership boundary of data flow.&lt;/p&gt;

&lt;p&gt;For example, in React, we often store data inside component state, then use &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt; to manage derived data and side effects.&lt;/p&gt;

&lt;p&gt;In Vue, the reactivity system is more fine-grained, but if the application’s data flow still revolves heavily around component scope, &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;watchEffect&lt;/code&gt;, or template consumption, data ownership can still become scattered.&lt;/p&gt;

&lt;p&gt;This is not about whether a framework is capable or not.&lt;/p&gt;

&lt;p&gt;It is about where the center of architectural thinking is placed.&lt;/p&gt;

&lt;p&gt;Frameworks usually care about this question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When should the UI update?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But when I was designing &lt;code&gt;signal-kernel&lt;/code&gt;, the question in my mind was different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After data changes, who should own the update logic?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These two questions may look similar, but they are fundamentally different.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Components Should Not Carry All Data Flow Responsibilities
&lt;/h2&gt;

&lt;p&gt;Components are great at describing UI. They are good at composing views, receiving props, handling events, and presenting state.&lt;/p&gt;

&lt;p&gt;But they are not ideal ownership boundaries for the entire data flow. This isn't obvious when your state is simple, but once derived state and async state start piling up, components are quickly crushed under responsibilities they were never meant to hold.&lt;/p&gt;

&lt;p&gt;Consider a common flow 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;userId changes
  → fetch user profile
  → fetch permissions
  → compute available actions
  → update form visibility
  → trigger validation
  → maybe render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this entire flow is placed inside a component, it often becomes something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useEffect&lt;/code&gt; fetch data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useMemo&lt;/code&gt; compute derived state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useEffect&lt;/code&gt; sync another state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useEffect&lt;/code&gt; trigger validation&lt;/li&gt;
&lt;li&gt;conditional render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, this may still look acceptable. But as requirements grow, the component slowly turns into a data-flow scheduler.&lt;/p&gt;

&lt;p&gt;It no longer only describes the UI. It also has to decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who depends on whom?&lt;/li&gt;
&lt;li&gt;Who should update first?&lt;/li&gt;
&lt;li&gt;Which values can be recomputed?&lt;/li&gt;
&lt;li&gt;Which side effects are allowed to run?&lt;/li&gt;
&lt;li&gt;Which async work needs to be cancelled?&lt;/li&gt;
&lt;li&gt;Which state must not drift?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the responsibility of the component has already gone far beyond render. And the most troublesome part is that these dependencies are usually not explicit. They are hidden inside hooks, watchers, callbacks, and dependency arrays.&lt;/p&gt;

&lt;p&gt;The system may still work on the surface, but the ownership of the data flow has been shattered.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boundary Problem of Derived State
&lt;/h2&gt;

&lt;p&gt;Derived state is where this architectural leak is most glaring. By derived state, I simply mean data that can be programmatically inferred from other data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cartItems&lt;/code&gt; → &lt;code&gt;totalPrice&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userRole&lt;/code&gt; + &lt;code&gt;permissions&lt;/code&gt; → &lt;code&gt;availableActions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;searchKeyword&lt;/code&gt; + &lt;code&gt;filters&lt;/code&gt; + &lt;code&gt;rawList&lt;/code&gt; → &lt;code&gt;visibleList&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ideally, these are just nodes in a reactive graph. When source state changes, the derived state naturally marks itself as stale and recomputes only when necessary.&lt;/p&gt;

&lt;p&gt;But in a render-centric codebase, these derivations are trapped inside components through &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;computed&lt;/code&gt;, or similar component-scoped mechanisms. This is fine in isolation. This is fine in isolation. The ownership boundary starts to blur, however, when that derived state is needed by an async task, a cache, or a business rule outside the UI layer.&lt;/p&gt;

&lt;p&gt;Suddenly, state that &lt;em&gt;looks&lt;/em&gt; like it belongs to the UI is actually carrying the weight of domain logic. This is the exact ambiguity of ownership I keep emphasizing.&lt;/p&gt;




&lt;h2&gt;
  
  
  It's About Ownership, Not Performance
&lt;/h2&gt;

&lt;p&gt;When people talk about signals or fine-grained reactivity, the first thing that usually comes to mind is performance.React re-renders; Solid updates the DOM directly; Vue tracks dependencies precisely; Signals bypass unnecessary recomputations.&lt;/p&gt;

&lt;p&gt;These are real, practical benefits. But over time, I’ve realized that performance is just the most visible byproduct. The actual paradigm shift is ownership.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Render-centric model&lt;/strong&gt;: The UI owns the data flow. Data mutations must pass through the UI lifecycle (hooks, component scopes) before they can be organized, derived, and synchronized.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graph-centric model&lt;/strong&gt;: The reactive graph owns the data flow. Events trigger signals, signals derive a computed graph, and effects, resources, and renders are all simply downstream consumers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This realization became my North Star while building signal-kernel. I wasn't trying to write another UI framework, nor was I trying to prove React or Vue wrong. I just wanted to answer one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If render is no longer the center of data flow, who should take over?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My answer is &lt;strong&gt;the reactive graph&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let Render Return to Its Proper Position
&lt;/h2&gt;

&lt;p&gt;When the reactive graph takes over, render is drastically simplified. It doesn't need to know how data is derived, it doesn't orchestrate update orders, and it doesn't juggle async consistency. It just reads the state it cares about and paints the screen.&lt;/p&gt;

&lt;p&gt;This isn't about downplaying the importance of the UI. It’s about letting the UI return to what it's actually good at. In this model, the boundaries are strict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal&lt;/strong&gt; owns the source state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computed&lt;/strong&gt; owns the derived state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource&lt;/strong&gt; owns the async state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effect&lt;/strong&gt; owns the side effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; owns the visual output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By extracting data flow from the render lifecycle, we give data its own independent ownership boundary. This is the core semantic philosophy behind &lt;code&gt;signal-kernel&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for React and Vue
&lt;/h2&gt;

&lt;p&gt;For React developers, this pain point is acute. Because React is fundamentally render-driven, data-flow issues inevitably turn into interrogations about &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, dependency arrays, and re-render optimization. This is exactly why large React codebases suffer from dependency array fatigue, stale closures, and effect overuse.&lt;/p&gt;

&lt;p&gt;For Vue, the situation is more nuanced. Vue already has a built-in reactivity system; &lt;code&gt;ref&lt;/code&gt;, &lt;code&gt;computed&lt;/code&gt;, and &lt;code&gt;watch&lt;/code&gt; describe data dependencies beautifully. A Vue developer reading this might think: "&lt;em&gt;Isn't Vue doing this already?&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;To an extent, yes. Vue brought reactive graphs into mainstream application development much earlier. But there is a key distinction: Vue’s reactivity can be used independently, but its primary design context is still the Vue application model.&lt;/p&gt;

&lt;p&gt;What signal-kernel explores is a framework-agnostic reactive graph. I'm not asking, "&lt;em&gt;Should we use signals to update Vue components?&lt;/em&gt;" I'm asking, "&lt;em&gt;Can we build a standalone data core that React, Vue, background jobs, async runtimes, and even AI agents can all consume simultaneously?&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;That’s why the React and Vue adapters for signal-kernel are intentionally thin. A framework adapter shouldn't seize control of the data flow; it should merely bridge the graph to the UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  A System You Can Actually Reason About
&lt;/h2&gt;

&lt;p&gt;When render relinquishes control, you don't just get better performance—you get a system you can actually reason about. You stop asking framework-specific syntax questions and start asking architectural ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this source state or derived state?&lt;/li&gt;
&lt;li&gt;Is this a synchronous derivation or an async result?&lt;/li&gt;
&lt;li&gt;Is this a data-layer effect or a UI-layer effect?&lt;/li&gt;
&lt;li&gt;Should this state live inside the component, or inside an independent reactive graph?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conversely, when everything is pushed back into the component lifecycle, profound architectural flaws are disguised as hook usage questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I write this dependency array?&lt;/li&gt;
&lt;li&gt;Why is this &lt;code&gt;useEffect&lt;/code&gt; firing twice?&lt;/li&gt;
&lt;li&gt;Should I wrap this in &lt;code&gt;useMemo&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;watchEffect&lt;/code&gt; too automatic?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions all have technical answers, but they mask the deeper, underlying problem: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is actually supposed to own this piece of the data flow?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Downgrading Render Into One of Many Outputs
&lt;/h2&gt;

&lt;p&gt;UI frameworks are incredibly important. React, Vue, and Solid have all solved very difficult problems in their own dimensions.&lt;/p&gt;

&lt;p&gt;But in complex frontend applications, we need to recognize one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Render should no longer be treated as the only center of data flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Only when data flow has its own ownership can a system move from UI-driven state management toward graph-driven state ownership.&lt;/p&gt;

&lt;p&gt;On the surface, &lt;a href="https://github.com/Luciano0322/signal-kernel" rel="noopener noreferrer"&gt;&lt;code&gt;signal-kernel&lt;/code&gt;&lt;/a&gt; is a signal library. But at its core, it is an exploration of how frontend data architecture can be rebuilt when render steps back and becomes just one kind of side effect.&lt;/p&gt;

&lt;p&gt;In the next article, I will continue breaking down this problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When state, derived state, and effect are mixed together, how does a system gradually lose its boundaries?&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>From Signals to Ownership: Why I Built a Dataflow Kernel</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 15 Jun 2026 07:17:57 +0000</pubDate>
      <link>https://dev.to/luciano0322/from-signals-to-ownership-why-i-built-a-dataflow-kernel-1h0f</link>
      <guid>https://dev.to/luciano0322/from-signals-to-ownership-why-i-built-a-dataflow-kernel-1h0f</guid>
      <description>&lt;p&gt;When I first started building &lt;code&gt;signal-kernel&lt;/code&gt;, I thought I was simply writing a signal library—a fine-grained reactive system not tied to any specific UI framework. On the surface, it had all the basic building blocks you would expect from a signal library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core primitives:&lt;/strong&gt; &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Computed&lt;/code&gt;, and &lt;code&gt;Effect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanisms:&lt;/strong&gt; dependency tracking and scheduling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensions:&lt;/strong&gt; async resources and framework adapters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It could build a reactive graph, track dependencies, update derived state precisely when data changed, and bridge the result into React or Vue. But the deeper I dug into the implementation, the more I realized the real problem wasn't about building another signal library. The core question was actually this: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In a complex application, who should truly own the dataflow?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fragmented Dataflow and the Derived State Problem
&lt;/h2&gt;

&lt;p&gt;In modern frontend applications, dataflow often feels natural at first. API data comes back, gets written into state, and components render the result. If we need derived data, we use &lt;code&gt;useMemo&lt;/code&gt;. If we need side effects, we use &lt;code&gt;useEffect&lt;/code&gt;. If we need async requests, we use a query library, and if we need shared state, we introduce a store.&lt;/p&gt;

&lt;p&gt;Individually, none of these solutions are wrong. React is not wrong. Vue is not wrong. TanStack Query is not wrong. Zustand and Jotai are not wrong either. The real problem appears when the system grows larger. At that point, the exact same logical dataflow starts getting split across multiple ownership models.&lt;/p&gt;

&lt;p&gt;Some state belongs to components, while other state belongs to a store. Some derived state is trapped inside the render phase, and async state lives inside a query cache. The system still works, but the boundaries of the dataflow start to depend heavily on conventions, team discipline, and developer memory. It becomes incredibly hard to answer one seemingly simple question: &lt;strong&gt;Who actually owns this derived state?&lt;/strong&gt; Is it the component, the store, the framework runtime, or just a temporary value calculated inside a hook?&lt;/p&gt;

&lt;h2&gt;
  
  
  Render Should Not Naturally Own the Entire Dataflow
&lt;/h2&gt;

&lt;p&gt;Frontend developers often treat render as the center of the system. This is completely understandable—what we look at and debug every day is the UI. So when data changes, our first instinct is usually: &lt;em&gt;"Which component needs to re-render?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But if we reverse the question, the architecture looks very different. When data changes, perhaps the first question should be: &lt;em&gt;"Which pieces of derived data are actually affected?"&lt;/em&gt; Render is simply one kind of side effect caused by data changes. It is extremely important, but it should not naturally own the entire dataflow.&lt;/p&gt;

&lt;p&gt;This became one of the core architectural lessons I learned: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The reactive graph should first describe the relationships between data, while render should only be one consumer of that graph.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  From Signal Library to Dataflow Kernel
&lt;/h2&gt;

&lt;p&gt;If I only saw &lt;code&gt;signal-kernel&lt;/code&gt; as a signal library, then most design questions would stay at the API surface: Should it feel more like Solid? Should computed values be lazy or eager? Should it support batching? These are important, but they hide the deeper issue: When a piece of state changes, who is responsible for deciding which derived states are affected?&lt;/p&gt;

&lt;p&gt;In a traditional component-driven model, this usually happens during render:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Data changes → component re-executes → derived data is recalculated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This model works beautifully for small applications, but it forces the lifecycle of derived state to follow the lifecycle of render. Once derived state becomes expensive, needs to be shared across frameworks, or needs to be deeply connected with async streams, this model becomes fragile.&lt;/p&gt;

&lt;p&gt;That is why the real goal of &lt;code&gt;signal-kernel&lt;/code&gt; was never simply to “reduce re-renders.” The goal was to &lt;strong&gt;detach derived dataflow from render ownership&lt;/strong&gt;. What I actually wanted to build was a dataflow kernel.&lt;/p&gt;

&lt;p&gt;A system that focuses on lower-level architectural concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How state becomes a pure source of truth.&lt;/li&gt;
&lt;li&gt;How derived state is tracked stably inside a reactive graph.&lt;/li&gt;
&lt;li&gt;How effects can be separated cleanly from the render lifecycle.&lt;/li&gt;
&lt;li&gt;How async work and continuously written streams can become part of the reactive graph while keeping clear state boundaries.&lt;/li&gt;
&lt;li&gt;How render can return to its proper role: a consumer, not the owner.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping the Boundary: Thin Framework Adapters
&lt;/h2&gt;

&lt;p&gt;This way of thinking also directly shaped how I designed the React and Vue adapters. At the beginning, I naturally wanted to provide many convenient hooks and APIs that felt familiar to framework users. But over time, I became much more conservative.&lt;/p&gt;

&lt;p&gt;If an adapter becomes too thick, it quietly pulls ownership back into the UI framework. And once that happens, the data independence that &lt;code&gt;signal-kernel&lt;/code&gt; is trying to preserve starts to fade away.&lt;/p&gt;

&lt;p&gt;A cleaner and more stable boundary should look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core owns the reactive graph.&lt;/li&gt;
&lt;li&gt;Async-runtime owns the consistency of async state.&lt;/li&gt;
&lt;li&gt;Framework adapters only subscribe to and read snapshots.&lt;/li&gt;
&lt;li&gt;Render only draws the current result to the screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An adapter should not redefine the dataflow. It should only be a transparent pipe into the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking Framework Responsibility
&lt;/h2&gt;

&lt;p&gt;I want to make one thing clear: this series is not meant to criticize React or any other UI tool. React is still excellent at handling UI consistency, and TanStack Query solves a very real, difficult server-state problem.&lt;/p&gt;

&lt;p&gt;What I want to explore is a deeper architectural question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When we habitually put all data logic into the model of a UI framework, are we accidentally making render responsible for too much?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If reactive dataflow itself can be an independent system, then what role should a UI framework actually play?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Was Really Studying Was Ownership
&lt;/h2&gt;

&lt;p&gt;While implementing the core and async-runtime of &lt;code&gt;signal-kernel&lt;/code&gt;, I kept running into a series of uncomfortable architectural questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After a Promise resolves, is it really just a simple &lt;code&gt;setState&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;If a stream keeps emitting chunks, who should own its stable value, status, and error?&lt;/li&gt;
&lt;li&gt;After a mutation succeeds, is invalidation the responsibility of the query cache, or the reactive graph?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these questions eventually point to the same word: &lt;strong&gt;Ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I originally thought I was building a signal library. But the more I built, the more I realized that what I was really studying was how the boundaries of dataflow should be redefined in increasingly complex frontend applications. APIs can change. Implementations can be replaced. But the core question remains the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When data changes, who is responsible for understanding that change?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is always render, then we are forced to keep layering workarounds around the UI framework. But if the answer is the reactive graph, then render can return to its most reasonable position. It is one outlet of the dataflow. It is one side effect. It is a consumer of the system, not the owner.&lt;/p&gt;

&lt;p&gt;That is the most important lesson I learned while building &lt;a href="https://github.com/Luciano0322/signal-kernel" rel="noopener noreferrer"&gt;&lt;code&gt;signal-kernel&lt;/code&gt;&lt;/a&gt;. If this idea resonates with you, I’m exploring it through &lt;a href="https://github.com/Luciano0322/signal-kernel" rel="noopener noreferrer"&gt;&lt;code&gt;signal-kernel&lt;/code&gt;&lt;/a&gt;, a framework-agnostic reactive dataflow kernel.&lt;/p&gt;

&lt;p&gt;The project is still evolving, but its direction is clear: &lt;strong&gt;render should be a consumer of the dataflow, not the owner.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the next article, I want to continue exploring this question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What actually changes in frontend architecture when render no longer owns the entire dataflow?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What I Learned After Building a Signal System from Scratch</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 08 Jun 2026 02:27:19 +0000</pubDate>
      <link>https://dev.to/luciano0322/what-i-learned-after-building-a-signal-system-from-scratch-280p</link>
      <guid>https://dev.to/luciano0322/what-i-learned-after-building-a-signal-system-from-scratch-280p</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;At this point, this series on Signals and fine-grained reactivity is temporarily coming to an end.&lt;/p&gt;

&lt;p&gt;This article will not introduce new technical details. Instead, I want to share some personal reflections and thoughts after writing the whole series.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Influence of Ryan Carniato
&lt;/h1&gt;

&lt;p&gt;In some sense, this entire series was inspired by Ryan Carniato, the creator of Solid.js.&lt;/p&gt;

&lt;p&gt;Ryan has always been able to point directly at the pain points we face in React development. As a senior React engineer, I often find myself thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yes, this is exactly the problem I run into every day.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solid.js is undoubtedly an excellent framework, although its learning curve can be high for beginners.&lt;/p&gt;

&lt;p&gt;However, from my perspective, many of its design choices feel more reasonable than the direction React has taken. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Effects without dependency arrays&lt;/li&gt;
&lt;li&gt;Separating mounted logic from cleanup logic&lt;/li&gt;
&lt;li&gt;JSX without the Virtual DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all things that many developers in the React community had asked for before, but they were never truly prioritized.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Dilemma of React and Next.js
&lt;/h1&gt;

&lt;p&gt;Over the past few years, React’s direction has become increasingly influenced by the needs of the Next.js ecosystem.&lt;/p&gt;

&lt;p&gt;Server Components introduced an important architectural shift, but they also forced many frontend engineers to adopt a model that still feels immature in everyday application development.&lt;/p&gt;

&lt;p&gt;To be clear, I am not saying Server Components are useless. They solve real problems around server-side data access, streaming, and reducing client-side JavaScript. But from a frontend developer’s perspective, they also make the mental model of React significantly more complex.&lt;/p&gt;

&lt;p&gt;To be honest, I sometimes miss the era of “Next.js without Server Components.” React was simpler back then. Its problems were obvious, but at least the boundaries were easier to reason about, and we had more freedom to choose our own solutions.&lt;/p&gt;

&lt;p&gt;People in the community have proposed ideas such as “Should React introduce signals?” or even “Should React explore a version without the Virtual DOM?” These ideas may not align naturally with the current product direction and incentives around React and Next.js, but I still think they are worth discussing seriously.&lt;/p&gt;

&lt;p&gt;My point is not that React is wrong or that the Virtual DOM is useless.&lt;/p&gt;

&lt;p&gt;The Virtual DOM is still a reasonable abstraction in many scenarios. For example, in cross-platform environments such as React Native, it provides a practical compromise between a declarative UI model and multiple rendering targets.&lt;/p&gt;

&lt;p&gt;My point is narrower:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Virtual DOM is not wrong. It is just no longer the only reasonable center of frontend architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the problem is fine-grained data dependency, update precision, and scheduler correctness, signals provide a different model that deserves serious consideration.&lt;/p&gt;

&lt;p&gt;What I see today is a React ecosystem entering a more conflicted phase. Although projects like TanStack are still doing excellent work to improve the developer experience, if a mature alternative appears in the future, I may no longer choose Next.js by default.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Misunderstandings
&lt;/h1&gt;

&lt;h2&gt;
  
  
  “Isn’t this just Jotai?”
&lt;/h2&gt;

&lt;p&gt;In the community, you may sometimes hear people say that Jotai — or the atomic state model in general — is basically the same thing as signals. Some even argue that because it is built closer to the framework ecosystem, it is more developer-friendly.&lt;/p&gt;

&lt;p&gt;But if you have followed this series and implemented the concepts step by step, or if you have ever implemented a reactive system yourself, you will understand that the two approaches are based on very different design philosophies.&lt;/p&gt;

&lt;p&gt;The key difference is whether the system handles dependencies.&lt;/p&gt;

&lt;p&gt;Below is a simple conceptual comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomic State: The Recoil / Jotai Family
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core idea&lt;/strong&gt;: Split state into the smallest possible units, often called state atoms. There is no implicit dependency tracking between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composition model&lt;/strong&gt;: Use selectors or derived functions to organize logic and compose state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update model&lt;/strong&gt;: When state changes, the system does not automatically notify downstream dependents. Instead, selectors are re-executed as pure functions.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to understand in a functional way: state is a value, and a selector is a pure function.&lt;/li&gt;
&lt;li&gt;However, the dependency graph is not explicit. Every read may require recomputation, which can lead to repeated calculations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Signals
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core idea&lt;/strong&gt;: Signals are also fine-grained state units, but they include an explicit dependency-tracking graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composition model&lt;/strong&gt;: &lt;code&gt;computed&lt;/code&gt; and &lt;code&gt;effect&lt;/code&gt; automatically register dependencies during execution. When a source value changes later, downstream subscribers are notified automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update model&lt;/strong&gt;: Usually push-based. Once a source value changes, downstream nodes are marked as stale, and the scheduler decides when to run updates.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoids unnecessary recomputation because the runtime knows who depends on whom.&lt;/li&gt;
&lt;li&gt;Update propagation is faster and more precise.&lt;/li&gt;
&lt;li&gt;Requires the runtime to manage a dependency graph.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Summary of the Difference
&lt;/h3&gt;

&lt;p&gt;In one sentence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomic state&lt;/strong&gt;: Splits state, but does not deeply manage dependencies. The system needs to “calculate again” through functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signals&lt;/strong&gt;: Splits state and adds dependency tracking. The system knows who needs to update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not handling dependencies does make the model easier to write and understand. Jotai also relies on the framework’s own state model, which naturally aligns with the framework lifecycle and avoids many synchronization problems.&lt;/p&gt;

&lt;p&gt;But the trade-off is that this model must rely on framework re-renders to guarantee correctness. Signals, on the other hand, can achieve more fine-grained updates through runtime-level dependency tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flow Difference
&lt;/h3&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%2Fz9p9jrgpua95tnn7yzqe.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%2Fz9p9jrgpua95tnn7yzqe.png" alt="Atomic state vs signal flow" width="800" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  “Isn’t this just Dependency Injection?”
&lt;/h2&gt;

&lt;p&gt;Another common comparison is to treat signals, especially when integrated with Vue or React, as something similar to Dependency Injection (DI) in the traditional OOP world.&lt;/p&gt;

&lt;p&gt;At first glance, it does look somewhat similar: you inject external state into a framework and use it inside the application.&lt;/p&gt;

&lt;p&gt;But the essential difference is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DI solves the composition and management of &lt;strong&gt;object dependencies&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Signals solve the tracking and update timing of &lt;strong&gt;data dependencies&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;They may look similar on the surface, but they solve problems at completely different dimensions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What Does DI Solve?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It answers: “Which object needs which service?”&lt;/li&gt;
&lt;li&gt;Timing: Construction time&lt;/li&gt;
&lt;li&gt;Keywords: dependency relationship, object composition&lt;/li&gt;
&lt;li&gt;Example: A &lt;code&gt;Car&lt;/code&gt; needs an &lt;code&gt;Engine&lt;/code&gt;, and the DI framework injects it for you.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Engine&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;h3&gt;
  
  
  What Do Signals Solve?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;They answer: “Which piece of data depends on which other piece of data?”&lt;/li&gt;
&lt;li&gt;Timing: Runtime&lt;/li&gt;
&lt;li&gt;Keywords: data tracking, state update propagation&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;totalPrice&lt;/code&gt; depends on &lt;code&gt;count * unitPrice&lt;/code&gt;, and it updates automatically when the source values change.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;unitPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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;totalPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;count&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="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;unitPrice&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Simple Clarification
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DI helps objects get the services they need.&lt;/li&gt;
&lt;li&gt;Signals help the system know who should update when data changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  “Signals are just another state management tool”
&lt;/h2&gt;

&lt;p&gt;Many people think signals are just another form of state management.&lt;/p&gt;

&lt;p&gt;But after going through the implementation details in this series, you can see that signals touch much more than state management.&lt;/p&gt;

&lt;p&gt;A state management library usually helps you organize where state lives and how it is updated.&lt;/p&gt;

&lt;p&gt;A signal system goes one layer deeper. It asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How are data dependencies tracked?&lt;/li&gt;
&lt;li&gt;How are invalidations propagated?&lt;/li&gt;
&lt;li&gt;When should derived values recompute?&lt;/li&gt;
&lt;li&gt;When should effects run?&lt;/li&gt;
&lt;li&gt;How should the scheduler preserve consistency?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why reducing signals to “just another state management tool” misses the point.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What signals challenge is not merely state management, but the long-standing assumption that the Virtual DOM must sit at the center of frontend architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That does not mean the Virtual DOM has no value. It means we should treat it as one architectural trade-off among many, not as the only valid mental model for UI development.&lt;/p&gt;

&lt;p&gt;Technical evolution is never just about code. It is also about incentives, existing knowledge systems, and the cost of changing how people think.&lt;/p&gt;

&lt;p&gt;Signals are uncomfortable because they move the discussion away from component re-rendering and toward data dependency itself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adjusting Your Mindset: Facing Irrational Criticism
&lt;/h1&gt;

&lt;p&gt;Some people will directly deny the value of signals. Some may even mock them with comments like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signals are useless.&lt;/p&gt;

&lt;p&gt;Dan already told you not to use them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These comments often carry more emotion or tribal position than technical understanding.&lt;/p&gt;

&lt;p&gt;To me, these voices are not a threat. They are more like a mirror:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you already understand the difference between atomic state and signals, you can easily see the blind spots in the argument.&lt;/li&gt;
&lt;li&gt;If you do not understand it yet, then it is a useful reminder that you should study the topic more deeply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, this does not mean we should blindly reject React’s design decisions either. React has solved many real engineering problems, and its ecosystem is still extremely valuable.&lt;/p&gt;

&lt;p&gt;But no framework author, no matter how experienced or influential, should become a reason for us to stop thinking.&lt;/p&gt;

&lt;p&gt;The healthier attitude is to return to concrete examples, implementation details, and trade-offs.&lt;/p&gt;

&lt;p&gt;So when you encounter this kind of criticism, the best response is not to rush into an argument.&lt;/p&gt;

&lt;p&gt;Return to the code. Return to the design principles.&lt;/p&gt;

&lt;p&gt;Once you can explain the difference in dependency tracking with a simple example, the mockery naturally loses its power.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Future of Signals
&lt;/h1&gt;

&lt;p&gt;Overall, I am quite optimistic about the future of signals.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/tc39/proposal-signals" rel="noopener noreferrer"&gt;TC39 Signals proposal&lt;/a&gt; from last year already shows that signals are becoming increasingly important in frontend development.&lt;/p&gt;

&lt;p&gt;Apart from React, which still seems to maintain a “React knows best” atmosphere, most mainstream frameworks have already entered a new era with signal-like ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue’s Vapor Mode&lt;/li&gt;
&lt;li&gt;Angular 17+ Signals&lt;/li&gt;
&lt;li&gt;Svelte Runes&lt;/li&gt;
&lt;li&gt;Preact Signals&lt;/li&gt;
&lt;li&gt;SolidJS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of them are even moving toward removing the unnecessary parts of the Virtual DOM, such as Vue’s Vapor Mode and SolidJS’s JSX compilation model.&lt;/p&gt;

&lt;p&gt;This means that developers who truly understand the core ideas behind signals will be much more comfortable in future frontend applications.&lt;/p&gt;

&lt;p&gt;In my own experience, once I understood these concepts, switching between different frameworks became much easier.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why I Started This Series
&lt;/h1&gt;

&lt;p&gt;The reason I started this series was that I was building my own open-source signal system library: &lt;a href="https://www.npmjs.com/package/@signal-kernel/core" rel="noopener noreferrer"&gt;signal-kernel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During that process, I kept running into challenges. Then I realized something important:&lt;/p&gt;

&lt;p&gt;Many of the “algorithms and data structures” we learned in school or practiced for interviews are actually useful here.&lt;/p&gt;

&lt;p&gt;This made me even more convinced of one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to design the foundation of a framework well, you must understand JavaScript fundamentals and computer science.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;p&gt;To me, this series is not just a set of technical notes. It is also part of my own journey as an engineer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It started from my frustration with React.&lt;/li&gt;
&lt;li&gt;It was inspired by Ryan Carniato’s ideas.&lt;/li&gt;
&lt;li&gt;It eventually led me back to the importance of JavaScript fundamentals and algorithms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest lesson I learned is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Frameworks will come and go, but fundamental knowledge and mental models are what allow you to go further.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This series only peels back the first layer of a framework engine: the reactive core.&lt;/p&gt;

&lt;p&gt;Of course, the reactive core is also one of the most important foundations of any framework. If we continue going deeper, we will run into topics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM rendering&lt;/li&gt;
&lt;li&gt;UI rendering schedulers&lt;/li&gt;
&lt;li&gt;Component models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every framework makes different decisions and trade-offs in these areas.&lt;/p&gt;

&lt;p&gt;These may become directions for my future research.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advice for Readers
&lt;/h1&gt;

&lt;p&gt;If you are currently learning React, you do not need to rush into Solid.js or any other framework.&lt;/p&gt;

&lt;p&gt;The truly important thing is not which framework you choose. The important thing is understanding the underlying concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The data flow from state → derivation → effect&lt;/li&gt;
&lt;li&gt;Why schedulers and dependency graph management matter&lt;/li&gt;
&lt;li&gt;The difference between push and pull&lt;/li&gt;
&lt;li&gt;The difference between eager and lazy execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you understand these ideas, you can quickly find your direction in any framework.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not just learn how to use a framework. Learn the logic underneath it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trends will change, but fundamental concepts are the real assets that engineers can carry with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal Implementation Reference
&lt;/h2&gt;

&lt;p&gt;As usual, I will also attach the implementation code from this series for reference:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Luciano0322/reactivity_lessons" rel="noopener noreferrer"&gt;reactivity_lessons&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository includes unit tests for each section, although only the parts that changed in each chapter are tested.&lt;/p&gt;

&lt;p&gt;I still hope readers will modify the examples themselves instead of simply copying and pasting the code.&lt;/p&gt;

&lt;p&gt;If you only copy the implementation directly, you will miss many opportunities to think through the details.&lt;/p&gt;

&lt;p&gt;The examples I provide are intentionally designed choices. In many cases, the data structures and subscription model may not fit every development scenario. However, they make the concepts easier for beginners to understand.&lt;/p&gt;

&lt;p&gt;I did not want to switch between too many algorithms and data structures during the explanation, because that would introduce unnecessary confusion.&lt;/p&gt;

&lt;p&gt;For more advanced discussion, I recommend focusing on Ryan Carniato’s article series, especially &lt;a href="https://dev.to/this-is-learning/derivations-in-reactivity-4fo1"&gt;Derivations in Reactivity&lt;/a&gt;, which explains many of the trade-offs behind today’s mainstream signal implementations.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>New article published: Building Reactive DevTools
A reactive system should not be a black box.
In this article, I explore how to make a signal-based runtime observable through node inspection, graph visualization, render counters, and hotspot tracking.</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 01 Jun 2026 06:06:33 +0000</pubDate>
      <link>https://dev.to/luciano0322/new-article-published-building-reactive-devtools-a-reactive-system-should-not-be-a-black-box-in-l3m</link>
      <guid>https://dev.to/luciano0322/new-article-published-building-reactive-devtools-a-reactive-system-should-not-be-a-black-box-in-l3m</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e" class="crayons-story__hidden-navigation-link"&gt;Building Reactive DevTools: Inspecting, Visualizing, and Profiling the Graph&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/luciano0322" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" alt="luciano0322 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/luciano0322" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Luciano0322
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Luciano0322
                
              
              &lt;div id="story-author-preview-content-3692092" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/luciano0322" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Luciano0322&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e" id="article-link-3692092"&gt;
          Building Reactive DevTools: Inspecting, Visualizing, and Profiling the Graph
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>performance</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building Reactive DevTools: Inspecting, Visualizing, and Profiling the Graph</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Mon, 01 Jun 2026 05:59:47 +0000</pubDate>
      <link>https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e</link>
      <guid>https://dev.to/luciano0322/building-reactive-devtools-inspecting-visualizing-and-profiling-the-graph-270e</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In the previous chapters, we explored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scheduler internals&lt;/li&gt;
&lt;li&gt;Memory and graph management&lt;/li&gt;
&lt;li&gt;Priority and layered scheduling&lt;/li&gt;
&lt;li&gt;Time-slicing and cooperative scheduling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these mechanisms are essential for making a reactivity system work correctly internally.&lt;/p&gt;

&lt;p&gt;But internal correctness alone is not enough.&lt;/p&gt;

&lt;p&gt;For developers, the more important question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we observe, debug, and understand the system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where DevTools and diagnostics become critical.&lt;/p&gt;




&lt;h1&gt;
  
  
  Inspecting Nodes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Why do we need &lt;code&gt;inspect()&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;One of the most common debugging needs during development is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What is the current value of this signal or computed?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the only solution is &lt;code&gt;console.log&lt;/code&gt;, debugging quickly becomes inconvenient and intrusive.&lt;/p&gt;

&lt;p&gt;A proper inspection layer gives developers visibility without polluting application logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Design
&lt;/h2&gt;

&lt;p&gt;The inspector should provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current &lt;code&gt;value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dependency relationships (&lt;code&gt;deps / subs&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whether the node is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stale&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disposed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// devtools.ts&lt;/span&gt;
&lt;span class="c1"&gt;// type reused from previous graph.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./graph.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Assign IDs using WeakMap without polluting Node structure&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;WeakMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&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;let&lt;/span&gt; &lt;span class="nx"&gt;seq&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ids&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;n&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;id&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="o"&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&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="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;seq&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;ids&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;n&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="k"&gt;return&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InspectSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nl"&gt;inDegree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outDegree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kind&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="nl"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Get a flat snapshot of a single node (non-recursive)&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;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;InspectSnapshot&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inDegree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&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="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;outDegree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;node&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subs&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;n&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&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;h2&gt;
  
  
  Debug-Friendly Logging
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logInspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&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;snap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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="s2"&gt;`[inspect] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snap&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="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)  in=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inDegree&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  out=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDegree&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&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="nx"&gt;length&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="s2"&gt;  deps ↑&lt;/span&gt;&lt;span class="dl"&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;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&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="k"&gt;else&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="s2"&gt;  deps ↑ (none)&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;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subs&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="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="s2"&gt;  subs ↓&lt;/span&gt;&lt;span class="dl"&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;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subs&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="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="s2"&gt;  subs ↓ (none)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a much friendlier debugging experience than raw logging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recursive Graph Inspection
&lt;/h2&gt;

&lt;p&gt;When debugging larger dependency chains, inspecting a single node is often not enough.&lt;/p&gt;

&lt;p&gt;We can recursively expand the graph while avoiding cycles.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inspectRecursive&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="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&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;type&lt;/span&gt; &lt;span class="nx"&gt;Row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subs&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;[]&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;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;dUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;dDown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;node&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="na"&gt;dUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dDown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}];&lt;/span&gt;

  &lt;span class="nx"&gt;seen&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;root&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;queue&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="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;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dDown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;()&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;fromId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;dUp&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="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;dep&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;node&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="nx"&gt;rows&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deps&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;seen&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;dep&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;seen&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;dep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="nx"&gt;queue&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="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;dUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dUp&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="na"&gt;dDown&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dDown&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="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;sub&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;rows&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subs&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;seen&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;sub&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;seen&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;sub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="nx"&gt;queue&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="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;dUp&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="na"&gt;dDown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dDown&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="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="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;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&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="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;seen&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;n&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&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;h2&gt;
  
  
  Mermaid Export
&lt;/h2&gt;

&lt;p&gt;We can even export subgraphs into Mermaid diagrams for documentation or DevTools visualization.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toMermaid&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="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inspectRecursive&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="nx"&gt;depth&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;lines&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="s2"&gt;graph TD&lt;/span&gt;&lt;span class="dl"&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;n&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;lines&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="s2"&gt;`  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_#&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;n&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="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;e&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;edges&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_#&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;b&lt;/span&gt; &lt;span class="o"&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;to&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_#&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;lines&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="s2"&gt;`  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="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;return&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  API Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;inspect(node)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fastest single-node snapshot&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;logInspect(node)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debug-friendly console tables&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;inspectRecursive(node, depth)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small graph expansion without infinite loops&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;toMermaid(node, depth)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Export subgraphs for docs or DevTools rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Graph Visualization
&lt;/h1&gt;

&lt;p&gt;As applications grow larger, textual inspection is no longer sufficient.&lt;/p&gt;

&lt;p&gt;At that point, we need dependency graph visualization.&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%2Fjnx4j94p187dkgqwqmm7.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%2Fjnx4j94p187dkgqwqmm7.png" alt="graph visualization" width="765" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signal nodes (blue) represent data sources&lt;/li&gt;
&lt;li&gt;Computed nodes (green) represent derived values&lt;/li&gt;
&lt;li&gt;Effect nodes (yellow) represent side effects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside DevTools, we could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click nodes to inspect current values&lt;/li&gt;
&lt;li&gt;Highlight &lt;code&gt;stale&lt;/code&gt; nodes&lt;/li&gt;
&lt;li&gt;Visualize &lt;code&gt;link/unlink&lt;/code&gt; operations&lt;/li&gt;
&lt;li&gt;Observe automatic cleanup and graph pruning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the dataflow fully observable.&lt;/p&gt;

&lt;p&gt;Instead of a black box, developers can literally see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which state triggered which update.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Render Counters
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In UI frameworks, one of the most common performance issues is over-rendering.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Components re-render repeatedly&lt;/li&gt;
&lt;li&gt;But nothing meaningful actually changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature Design
&lt;/h2&gt;

&lt;p&gt;A render counter can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increment on each render&lt;/li&gt;
&lt;li&gt;Display a small overlay badge&lt;/li&gt;
&lt;li&gt;Aggregate render statistics in DevTools&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;This helps developers identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unnecessary re-renders&lt;/li&gt;
&lt;li&gt;Missing memoization&lt;/li&gt;
&lt;li&gt;Poor equality strategies&lt;/li&gt;
&lt;li&gt;Expensive recomputation chains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also guides optimization decisions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;memo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shallowEqual&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;computed caching&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Hotspot Tracking
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Why Track Hotspots?
&lt;/h2&gt;

&lt;p&gt;In large applications, knowing that something updated is not enough.&lt;/p&gt;

&lt;p&gt;We also need to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which nodes update the most frequently?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where hotspot profiling becomes valuable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Design
&lt;/h2&gt;

&lt;p&gt;We can track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update counts&lt;/li&gt;
&lt;li&gt;Update frequency&lt;/li&gt;
&lt;li&gt;Execution duration&lt;/li&gt;
&lt;li&gt;Graph degree statistics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And combine them with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heatmaps&lt;/li&gt;
&lt;li&gt;Timeline views&lt;/li&gt;
&lt;li&gt;Priority scheduling traces&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Hotspot Implementation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;hotspot.ts&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hotspot.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../graph.js&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;type&lt;/span&gt; &lt;span class="nx"&gt;HotspotStats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastTs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;freqPerMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;durTotal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;durCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&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;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;WeakMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HotspotStats&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;liveNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&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;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&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;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="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;?.()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frequency&lt;/li&gt;
&lt;li&gt;Total execution time&lt;/li&gt;
&lt;li&gt;Average execution time&lt;/li&gt;
&lt;li&gt;Update counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without modifying the core graph structure itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Tracking Signal Writes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;recordUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only inject instrumentation where actual work happens.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;signal.set()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;computed.recompute()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;effect.run()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the core runtime clean and minimally invasive.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Wrapping Computation Timing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;withTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;// recompute logic&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recomputation frequency&lt;/li&gt;
&lt;li&gt;average execution duration&lt;/li&gt;
&lt;li&gt;expensive reactive chains&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Example Usage
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;logTopHotspots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;freq&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;allNodes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nf"&gt;logTopHotspots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;avgTime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;allNodes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nf"&gt;logTopHotspots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;allNodes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets us quickly identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;high-frequency nodes&lt;/li&gt;
&lt;li&gt;expensive computations&lt;/li&gt;
&lt;li&gt;reactive bottlenecks&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Game Loops
&lt;/h2&gt;

&lt;p&gt;Identify states updating every frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forms
&lt;/h2&gt;

&lt;p&gt;Detect extremely hot input fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Visualization
&lt;/h2&gt;

&lt;p&gt;Locate nodes responsible for expensive re-renders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async Systems
&lt;/h2&gt;

&lt;p&gt;Observe retry storms or invalidation cascades.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why DevTools Matter
&lt;/h1&gt;

&lt;p&gt;DevTools are not just debugging utilities.&lt;/p&gt;

&lt;p&gt;They also help developers:&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Mental Models
&lt;/h2&gt;

&lt;p&gt;Understand how the reactive graph actually flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize Performance
&lt;/h2&gt;

&lt;p&gt;Locate bottlenecks quickly instead of guessing blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn Reactivity
&lt;/h2&gt;

&lt;p&gt;Make invisible runtime behavior visible and intuitive.&lt;/p&gt;




&lt;h1&gt;
  
  
  Possible Future Directions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Timeline Profiling
&lt;/h2&gt;

&lt;p&gt;Visualize update propagation over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Priority-Aware Diagnostics
&lt;/h2&gt;

&lt;p&gt;Inspect how different scheduler priorities interact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Suggestions
&lt;/h2&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“This signal updates too frequently.”&lt;/li&gt;
&lt;li&gt;“Consider memoizing this computed.”&lt;/li&gt;
&lt;li&gt;“This effect causes excessive downstream invalidation.”&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;By adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;node inspection&lt;/li&gt;
&lt;li&gt;graph visualization&lt;/li&gt;
&lt;li&gt;render counters&lt;/li&gt;
&lt;li&gt;hotspot profiling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we transform the reactive system from a black box into an observable runtime.&lt;/p&gt;

&lt;p&gt;This not only improves debugging and optimization,&lt;br&gt;
but also helps developers truly understand how reactivity works internally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DevTools are not just developer conveniences —&lt;br&gt;
they are part of how a runtime teaches its own architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At this point, I’ve shared most of the core knowledge I gained while implementing this series.&lt;/p&gt;

&lt;p&gt;There are still many unexplored trade-offs and architectural differences across reactive libraries, and those differences often determine why certain libraries perform better in specific scenarios.&lt;/p&gt;

&lt;p&gt;In the next article, I’d like to share some personal reflections and lessons learned from writing this entire series.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I published a new article in my Signals series.
In this article, I explore how cooperative scheduling, shouldYield(), and task chunking help avoid blocking the JavaScript main thread.</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Tue, 26 May 2026 03:17:32 +0000</pubDate>
      <link>https://dev.to/luciano0322/i-published-a-new-article-in-my-signals-series-in-this-article-i-explore-how-cooperative-587k</link>
      <guid>https://dev.to/luciano0322/i-published-a-new-article-in-my-signals-series-in-this-article-i-explore-how-cooperative-587k</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0" class="crayons-story__hidden-navigation-link"&gt;How React-Style Time-Slicing Keeps UIs Responsive&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/luciano0322" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" alt="luciano0322 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/luciano0322" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Luciano0322
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Luciano0322
                
              
              &lt;div id="story-author-preview-content-3691965" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/luciano0322" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Luciano0322&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0" id="article-link-3691965"&gt;
          How React-Style Time-Slicing Keeps UIs Responsive
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>performance</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How React-Style Time-Slicing Keeps UIs Responsive</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Tue, 26 May 2026 03:11:13 +0000</pubDate>
      <link>https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0</link>
      <guid>https://dev.to/luciano0322/how-react-style-time-slicing-keeps-uis-responsive-2lp0</guid>
      <description>&lt;h2&gt;
  
  
  Quick Recap
&lt;/h2&gt;

&lt;p&gt;In the previous article, we introduced &lt;strong&gt;priority-based and layered schedulers&lt;/strong&gt;, solving the problem of &lt;strong&gt;“which tasks should run first.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, real-world applications introduce another challenge:&lt;br&gt;
&lt;strong&gt;long-running tasks can still block the main thread.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To keep applications responsive, a scheduler must also support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time-Slicing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cooperative Scheduling&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  The Problem: Long Tasks Blocking the Main Thread
&lt;/h1&gt;

&lt;p&gt;Imagine this scenario:&lt;/p&gt;

&lt;p&gt;The UI thread is executing a &lt;strong&gt;large rendering task&lt;/strong&gt; — for example, updating 5000 list items at once.&lt;/p&gt;

&lt;p&gt;That operation might take tens of milliseconds, or even exceed 100ms before finishing.&lt;/p&gt;

&lt;p&gt;During that time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mouse movement and keyboard input cannot respond immediately&lt;/li&gt;
&lt;li&gt;The UI may freeze and drop below 60 FPS&lt;/li&gt;
&lt;li&gt;Users experience visible stuttering and lag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Priority alone is not enough.&lt;/p&gt;

&lt;p&gt;Even if a task has the correct priority, once execution begins, it can still monopolize the main thread.&lt;/p&gt;


&lt;h1&gt;
  
  
  Time-Slicing
&lt;/h1&gt;

&lt;p&gt;The core idea behind &lt;strong&gt;Time-Slicing&lt;/strong&gt; is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Split a long task into smaller chunks and periodically yield control back to the main thread.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A task is divided into smaller chunks&lt;/li&gt;
&lt;li&gt;After each chunk, the scheduler checks whether there is remaining execution time&lt;/li&gt;
&lt;li&gt;If not → pause execution and continue later during the next available frame or idle period&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User input and animations remain responsive&lt;/li&gt;
&lt;li&gt;Background work completes progressively over time&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  Cooperative Scheduling
&lt;/h1&gt;

&lt;p&gt;In operating systems, there are two major scheduling models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preemptive Scheduling&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cooperative Scheduling&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In JavaScript’s single-threaded environment, true preemption is impossible.&lt;/p&gt;
&lt;h2&gt;
  
  
  So frameworks adopt cooperative scheduling instead:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tasks voluntarily check whether they should yield (&lt;code&gt;shouldYield()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If interrupted, the remaining work is re-queued for later execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is essentially the strategy used by React Concurrent Mode.&lt;/p&gt;


&lt;h1&gt;
  
  
  Example Implementation
&lt;/h1&gt;

&lt;p&gt;Here is a simplified example of a &lt;strong&gt;Time-Slicing + Cooperative Scheduler&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;deadline&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="kd"&gt;function&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&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;runWithTimeSlicing&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;work&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="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timeSlice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;timeSlice&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="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="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="nf"&gt;work&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;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Not finished → continue later&lt;/span&gt;
  &lt;span class="nf"&gt;queueMicrotask&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;runWithTimeSlicing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeSlice&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;h2&gt;
  
  
  Key Idea
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Each execution is allowed to run for only &lt;code&gt;timeSlice&lt;/code&gt; milliseconds&lt;/li&gt;
&lt;li&gt;Once the budget is exceeded, control returns to the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents long-running tasks from blocking the main thread entirely.&lt;/p&gt;




&lt;h1&gt;
  
  
  Combining Time-Slicing with Priorities
&lt;/h1&gt;

&lt;p&gt;On top of a layered priority scheduler, we can integrate Time-Slicing strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Immediate / High Priority&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute immediately without slicing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Normal Priority&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use time-slicing and process incrementally&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Low / Idle Priority&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;requestIdleCallback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run only when the browser is idle&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the system to balance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time interactions&lt;/li&gt;
&lt;li&gt;Heavy background updates&lt;/li&gt;
&lt;li&gt;Overall UI smoothness&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Time-Slicing Scheduler Flow
&lt;/h1&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%2Funzsf4a6t2pg4pg2v252.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%2Funzsf4a6t2pg4pg2v252.png" alt="Time-slicing scheduler" width="800" height="815"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Priority scheduling solves the question of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Which task should run first?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Time-Slicing and Cooperative Scheduling solve another equally important problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we avoid blocking the UI?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Together, these techniques allow systems such as Signals, React, and Vue to remain responsive even under massive update workloads.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll explore &lt;strong&gt;DevTools and Diagnostics&lt;/strong&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspecting reactive nodes&lt;/li&gt;
&lt;li&gt;Dependency graph visualization&lt;/li&gt;
&lt;li&gt;Render counters&lt;/li&gt;
&lt;li&gt;Hotspot tracing&lt;/li&gt;
&lt;li&gt;Scheduler debugging tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help us better understand how signals and schedulers behave in real-world applications.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>In my latest signal article, I explore how priority queues and layered execution can help a scheduler preserve interaction responsiveness while still maximizing performance utilization.</title>
      <dc:creator>Luciano0322</dc:creator>
      <pubDate>Thu, 21 May 2026 05:22:37 +0000</pubDate>
      <link>https://dev.to/luciano0322/in-my-latest-signal-kernel-article-i-explore-how-priority-queues-and-layered-execution-can-help-a-51eh</link>
      <guid>https://dev.to/luciano0322/in-my-latest-signal-kernel-article-i-explore-how-priority-queues-and-layered-execution-can-help-a-51eh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/luciano0322/building-a-smarter-scheduler-priority-queues-and-layered-execution-3g6e" class="crayons-story__hidden-navigation-link"&gt;Building a Smarter Scheduler: Priority Queues and Layered Execution&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/luciano0322" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" alt="luciano0322 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/luciano0322" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Luciano0322
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Luciano0322
                
              
              &lt;div id="story-author-preview-content-3647393" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/luciano0322" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964885%2F18f31678-b62e-4519-8ee6-21f434e4b29c.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Luciano0322&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/luciano0322/building-a-smarter-scheduler-priority-queues-and-layered-execution-3g6e" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/luciano0322/building-a-smarter-scheduler-priority-queues-and-layered-execution-3g6e" id="article-link-3647393"&gt;
          Building a Smarter Scheduler: Priority Queues and Layered Execution
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/luciano0322/building-a-smarter-scheduler-priority-queues-and-layered-execution-3g6e" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/luciano0322/building-a-smarter-scheduler-priority-queues-and-layered-execution-3g6e#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>algorithms</category>
      <category>architecture</category>
      <category>computerscience</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
