<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Emilio Franco on Medium]]></title>
        <description><![CDATA[Stories by Emilio Franco on Medium]]></description>
        <link>https://medium.com/@kiljaja?source=rss-002336fae419------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Stories by Emilio Franco on Medium</title>
            <link>https://medium.com/@kiljaja?source=rss-002336fae419------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 05:44:20 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@kiljaja/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Reactivity Without the Re-Render Tax]]></title>
            <link>https://layers.engineering/reactivity-without-the-re-render-tax-ad894dcdf067?source=rss-002336fae419------2</link>
            <guid isPermaLink="false">https://medium.com/p/ad894dcdf067</guid>
            <category><![CDATA[ecommerce]]></category>
            <category><![CDATA[shopify]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Emilio Franco]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 15:41:07 GMT</pubDate>
            <atom:updated>2026-03-23T15:41:07.627Z</atom:updated>
            <content:encoded><![CDATA[<p>Customer clicks a variant. React re-renders the whole product card. The image. The reviews. The breadcrumbs. All of it—just to update a price. Your cart drawer re-renders 47 components to change one number from 9 to 10.</p><p>That&#39;s the re-render tax. And every framework has spent the last three years trying to stop paying it.</p><h3>The Problem</h3><p>Traditional state management works top-down. You update state at the top, the framework walks the entire tree below it, diffing every component to figure out what <em>actually</em> changed.</p><pre>setState({ price: 149.99 })<br><br>        [App]           ← re-renders<br>         |<br>    [ProductPage]       ← re-renders<br>     /    |     \<br>[Image] [Price] [Reviews]  ← ALL re-render<br>                               (only Price changed)</pre><p>Most of those re-renders produce identical output. The framework diffs them, throws the diff away, and moves on. It&#39;s busywork.</p><p>With signals, the update skips the tree entirely:</p><pre>price.value = 149.99<br><br>        [App]           ← untouched<br>         |<br>    [ProductPage]       ← untouched<br>     /    |     \<br>[Image] [Price] [Reviews]<br>           ↑<br>      only this updates</pre><p>No tree walk. No diffing. The signal knows exactly which DOM node subscribed to it and updates that node directly.</p><h3>Three Primitives, That&#39;s It</h3><p>Every signals implementation boils down to three concepts:</p><p>Signal — a reactive container. Holds a value. Notifies subscribers when it changes.</p><pre>import { signal } from &#39;@preact/signals-core&#39;<br><br>const cartCount = signal(0)<br>const variantPrice = signal(29.99)<br><br>// Read<br>console.log(cartCount.value) // 0<br><br>// Write — subscribers update automatically<br>cartCount.value = 3</pre><p>Computed — a derived value. Recalculates only when its dependencies change.</p><pre>import { signal, computed } from &#39;@preact/signals-core&#39;<br><br>const quantity = signal(2)<br>const unitPrice = signal(49.99)<br><br>const lineTotal = computed(() =&gt; quantity.value * unitPrice.value)<br>// lineTotal.value → 99.98<br><br>quantity.value = 3<br>// lineTotal.value → 149.97 (recalculated automatically)</pre><p>Effect — a side effect. Runs when tracked signals change.</p><pre>import { signal, effect } from &#39;@preact/signals-core&#39;<br><br>const cartCount = signal(0)<br><br>effect(() =&gt; {<br>  document.querySelector(&#39;.cart-badge&#39;).textContent = cartCount.value<br>})<br><br>// Later: update the signal, DOM updates automatically<br>cartCount.value = 5</pre><p>That&#39;s the whole API. Everything else is sugar on top.</p><h3>Shopify Example: Reactive Cart Price</h3><p>Here&#39;s a variant price that updates without re-rendering the product card:</p><pre>import { signal, computed, effect } from &#39;@preact/signals-core&#39;<br><br>// Variant data from Liquid or the AJAX API<br>const variants = [<br>  { id: 1, title: &#39;Small&#39;, price: 2999 },<br>  { id: 2, title: &#39;Medium&#39;, price: 3999 },<br>  { id: 3, title: &#39;Large&#39;, price: 4999 },<br>]<br><br>const selectedId = signal(1)<br>const quantity = signal(1)<br><br>const selectedVariant = computed(() =&gt;<br>  variants.find(v =&gt; v.id === selectedId.value)<br>)<br><br>const displayPrice = computed(() =&gt; {<br>  const cents = selectedVariant.value.price * quantity.value<br>  return &#39;$&#39; + (cents / 100).toFixed(2)<br>})<br><br>// Wire to DOM — only the price element updates<br>effect(() =&gt; {<br>  document.querySelector(&#39;.price--current&#39;).textContent = displayPrice.value<br>})<br><br>// Variant selector changes<br>document.querySelector(&#39;.variant-select&#39;).addEventListener(&#39;change&#39;, (e) =&gt; {<br>  selectedId.value = Number(e.target.value)<br>})</pre><p>Select &quot;Large&quot; — the price text node updates. The product image, reviews, and cart button don&#39;t re-render. They were never involved.</p><h3>One Library, Every Framework</h3><p>@preact/signals-core is framework-agnostic. Same import, same API, same .value access—regardless of what renders your templates. Here&#39;s the same reactive cart price wired into five frameworks.</p><h3>React</h3><pre>import { signal, computed } from &#39;@preact/signals-react&#39;<br>const selectedId = signal(1)<br>const quantity = signal(1)<br>const displayPrice = computed(() =&gt; {<br>  const variant = variants.find(v =&gt; v.id === selectedId.value)<br>  return &#39;$&#39; + ((variant.price * quantity.value) / 100).toFixed(2)<br>})<br>function Price() {<br>  return &lt;span className=&quot;price--current&quot;&gt;{displayPrice}&lt;/span&gt;<br>}</pre><p>@preact/signals-react wraps the core so signals render directly in JSX. No hooks, no selectors. Only Price updates when the variant changes.</p><h3>Vue 3</h3><pre>&lt;script setup&gt;<br>import { signal, computed, effect } from &#39;@preact/signals-core&#39;<br>import { ref, onMounted, onUnmounted } from &#39;vue&#39;const selectedId = signal(1)<br>const quantity = signal(1)<br>const displayPrice = computed(() =&gt; {<br>  const variant = variants.find(v =&gt; v.id === selectedId.value)<br>  return &#39;$&#39; + ((variant.price * quantity.value) / 100).toFixed(2)<br>})<br>// Bridge: sync signal into Vue&#39;s reactivity<br>const price = ref(displayPrice.value)<br>let dispose<br>onMounted(() =&gt; {<br>  dispose = effect(() =&gt; { price.value = displayPrice.value })<br>})<br>onUnmounted(() =&gt; dispose?.())<br>&lt;/script&gt;<br>&lt;template&gt;<br>  &lt;span class=&quot;price--current&quot;&gt;{{ price }}&lt;/span&gt;<br>&lt;/template&gt;<br></pre><p>Vue has its own signals (ref, computed). The bridge is a thin effect that pushes signal values into Vue&#39;s reactivity system. Useful when you want shared state logic across a Vue + React codebase.</p><h3>Svelte 5</h3><pre>&lt;script&gt;<br>import { signal, computed, effect } from &#39;@preact/signals-core&#39;<br>import { onMount, onDestroy } from &#39;svelte&#39;const selectedId = signal(1)<br>const quantity = signal(1)<br>const displayPrice = computed(() =&gt; {<br>  const variant = variants.find(v =&gt; v.id === selectedId.value)<br>  return &#39;$&#39; + ((variant.price * quantity.value) / 100).toFixed(2)<br>})<br>// Bridge: sync signal into Svelte&#39;s reactivity<br>let price = $state(displayPrice.value)<br>let dispose<br>onMount(() =&gt; {<br>  dispose = effect(() =&gt; { price = displayPrice.value })<br>})<br>onDestroy(() =&gt; dispose?.())<br>&lt;/script&gt;<br>&lt;span class=&quot;price--current&quot;&gt;{price}&lt;/span&gt;<br></pre><p>Same pattern. The effect pushes signal changes into a $state variable that Svelte knows how to render. The reactive logic stays framework-agnostic.</p><h3>Solid</h3><pre>import { signal, computed, effect } from &#39;@preact/signals-core&#39;<br>import { createSignal, onMount, onCleanup } from &#39;solid-js&#39;<br>const selectedId = signal(1)<br>const quantity = signal(1)<br>const displayPrice = computed(() =&gt; {<br>  const variant = variants.find(v =&gt; v.id === selectedId.value)<br>  return &#39;$&#39; + ((variant.price * quantity.value) / 100).toFixed(2)<br>})<br>function Price() {<br>  // Bridge: sync signal into Solid&#39;s reactivity<br>  const [price, setPrice] = createSignal(displayPrice.value)<br>  onMount(() =&gt; {<br>    const dispose = effect(() =&gt; setPrice(displayPrice.value))<br>    onCleanup(dispose)<br>  })<br>  return &lt;span class=&quot;price--current&quot;&gt;{price()}&lt;/span&gt;<br>}</pre><p>Solid already has fine-grained reactivity. The bridge is only useful if you’re sharing state logic with non-Solid code. Otherwise, use Solid’s native createSignal.</p><h3>Angular</h3><pre>import { Component, signal as ngSignal, effect as ngEffect } from &#39;@angular/core&#39;<br>import { signal, computed, effect } from &#39;@preact/signals-core&#39;<br>const selectedId = signal(1)<br>const quantity = signal(1)<br>const displayPrice = computed(() =&gt; {<br>  const variant = variants.find(v =&gt; v.id === selectedId.value)<br>  return &#39;$&#39; + ((variant.price * quantity.value) / 100).toFixed(2)<br>})<br>@Component({<br>  selector: &#39;app-price&#39;,<br>  template: `&lt;span class=&quot;price--current&quot;&gt;{{ price() }}&lt;/span&gt;`,<br>})<br>export class PriceComponent {<br>  price = ngSignal(displayPrice.value)<br>  constructor() {<br>    // Bridge: sync @preact/signals-core into Angular signals<br>    effect(() =&gt; this.price.set(displayPrice.value))<br>  }<br>}</pre><p>Angular v16+ has its own signals. The bridge syncs @preact/signals-core into Angular&#39;s change detection. Same story: shared logic, framework-specific rendering.</p><h3>The Pattern</h3><p>Every framework integration follows the same shape:</p><pre>@preact/signals-core (shared logic)<br>        |<br>    effect(() =&gt; {<br>        |<br>  framework.setState(signal.value)  ← bridge<br> })</pre><p>The reactive logic lives in @preact/signals-core. A thin effect bridges it into whatever reactivity system renders your templates. One source of truth, many renderers.</p><h3>Quick Reference</h3><p><strong>React</strong> — built-in bridge, automatic cleanup. Package: @preact/signals-react</p><p><strong>Vue 3: </strong>bridge via effect, cleanup via onUnmounted. Package: @preact/signals-core</p><p><strong>Svelte 5: </strong>bridge via effect → $state, cleanup via onDestroy. Package: @preact/signals-core</p><p><strong>Solid: </strong>bridge via effect → createSignal, cleanup via onCleanup. Package: @preact/signals-core</p><p><strong>Angular: </strong>bridge via effect → signal().set, automatic cleanup (injection context). Package: @preact/signals-core</p><p>One library. One API. Five frameworks.</p><h3>The Takeaway</h3><p>@preact/signals-core is 1.6kB of framework-agnostic reactivity. Write your state logic once with signal, computed, and effect. Bridge it into whatever renders your UI with a single effect call.</p><p>The re-render tax is optional now. Stop paying it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1nwz_HOFshIR4MJZq8310g.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ad894dcdf067" width="1" height="1" alt=""><hr><p><a href="https://layers.engineering/reactivity-without-the-re-render-tax-ad894dcdf067">Reactivity Without the Re-Render Tax</a> was originally published in <a href="https://layers.engineering">Engineering @ Layers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>