<?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: WingsDevelopment</title>
    <description>The latest articles on DEV Community by WingsDevelopment (@92srdjan).</description>
    <link>https://dev.to/92srdjan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700427%2F1fd8004a-e386-4292-9f54-8d791395ff67.png</url>
      <title>DEV Community: WingsDevelopment</title>
      <link>https://dev.to/92srdjan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/92srdjan"/>
    <language>en</language>
    <item>
      <title>Handling Multiple Data Sources in DeFi Frontend Architecture</title>
      <dc:creator>WingsDevelopment</dc:creator>
      <pubDate>Tue, 27 Jan 2026 09:54:06 +0000</pubDate>
      <link>https://dev.to/92srdjan/handling-multiple-data-sources-in-defi-frontend-architecture-27kk</link>
      <guid>https://dev.to/92srdjan/handling-multiple-data-sources-in-defi-frontend-architecture-27kk</guid>
      <description>&lt;p&gt;Since a lot of people showed interest in my last blog post about architecture, I decided to start a &lt;strong&gt;blog series&lt;/strong&gt; to address common questions and dilemmas you might be facing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context: Why This Series Exists
&lt;/h2&gt;

&lt;p&gt;If you haven’t seen it yet, this post is a continuation of a previous article I wrote while working on Euler’s frontend architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/eulerfinance/status/2010734949955928472" rel="noopener noreferrer"&gt;&lt;strong&gt;The Architecture That Fixed Euler’s Frontend Performance&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Taking a deeper look at fetch → mapper → UI approach:&lt;br&gt;
&lt;a href="https://dev.to/92srdjan/the-web2-mental-model-doesnt-work-in-web3-18on"&gt;&lt;strong&gt;The Web2 Mental Model Doesn’t Work in Web3&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Let’s begin by focusing on &lt;strong&gt;architectural problems and the bigger picture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One of the most common issues in front-end applications is handling &lt;strong&gt;data fetching from multiple sources&lt;/strong&gt;. On the happy path, everything works fine, but the real question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when one or more of those sources fail?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In DeFi applications, data usually comes from multiple places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the blockchain (RPC),&lt;/li&gt;
&lt;li&gt;subgraphs,&lt;/li&gt;
&lt;li&gt;third-party services (Merkle distributors, reward APIs),&lt;/li&gt;
&lt;li&gt;price feeds, and similar APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every query has or should have all of these dependencies tightly coupled together.&lt;/p&gt;

&lt;p&gt;But let’s look at a simple example that almost every DeFi app needs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch token balances, display them in a table, and also show the total value in USD.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first glance, this looks trivial.&lt;/p&gt;

&lt;p&gt;In practice, it’s not.&lt;/p&gt;


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

&lt;p&gt;With a typical hook-based approach, the main issue is that &lt;strong&gt;these requests are not synchronous&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There’s no reliable way to know when &lt;em&gt;all&lt;/em&gt; balances, symbols, decimals, and prices have been fetched.&lt;/p&gt;

&lt;p&gt;And poor UX is only one reason to look for a different approach. There are many others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper caching — &lt;strong&gt;reducing RPC call costs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Leveraging wagmi batching&lt;/li&gt;
&lt;li&gt;Minimal query cache invalidation&lt;/li&gt;
&lt;li&gt;Cleaner code and easier maintenance&lt;/li&gt;
&lt;li&gt;Better testability&lt;/li&gt;
&lt;li&gt;Increased robustness and fewer bugs&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  A Mapper-Based Approach
&lt;/h2&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%2Fd65m6h5ofku7r3rrr7p1.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%2Fd65m6h5ofku7r3rrr7p1.png" alt=" " width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s start with a mapper-based solution.&lt;/p&gt;

&lt;p&gt;Here’s an example of a &lt;code&gt;balancesMapper&lt;/code&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetches balances,&lt;/li&gt;
&lt;li&gt;fetches token prices,&lt;/li&gt;
&lt;li&gt;calculates the total USD value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While fetchers are handling caching under the hood, more on this &lt;a href="https://dev.to/92srdjan/the-web2-mental-model-doesnt-work-in-web3-18on"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&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;balancesMapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;BalancesMapperOptions&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DisplayBalances&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pricesResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="c1"&gt;// Let wagmi batch these RPC calls&lt;/span&gt;
    &lt;span class="c1"&gt;// If this fails, the whole mapper fails&lt;/span&gt;
    &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;tokens&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;token&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;balanceMapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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;// Different data source (HTTP fetch), so we make this safe&lt;/span&gt;
    &lt;span class="c1"&gt;// If this fails, we can still display balances&lt;/span&gt;
    &lt;span class="nf"&gt;safeCall&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;tokenPricesMapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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="na"&gt;totalValueUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SafeResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ViewNumber&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;pricesResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pricesResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pricesResult&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalValueUsd&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;Here we’re fetching data from &lt;strong&gt;two different sources&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the blockchain (balances),&lt;/li&gt;
&lt;li&gt;a price API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From a UI perspective, the goal is to display &lt;strong&gt;as much information as possible&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the price API fails → we can still show balances.&lt;/li&gt;
&lt;li&gt;If the RPC call fails → price data becomes useless in this scenario.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this query should &lt;strong&gt;fully fail&lt;/strong&gt; if the RPC call fails, but &lt;strong&gt;partially degrade&lt;/strong&gt; if the price API does.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing a Mapper Starts With the UI
&lt;/h2&gt;

&lt;p&gt;When creating a query mapper, the first question should &lt;strong&gt;not&lt;/strong&gt; be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I fetch this data?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“What does the UI actually need, and what is it allowed to show if parts of this fail?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the most important responsibility of a mapper.&lt;/p&gt;

&lt;p&gt;A mapper is not just a place to combine fetch calls — it’s where you &lt;strong&gt;define failure boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For every mapper, you should explicitly decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which data is &lt;strong&gt;required&lt;/strong&gt; for the UI to function&lt;/li&gt;
&lt;li&gt;Which data is &lt;strong&gt;optional&lt;/strong&gt; and can fail without breaking the experience&lt;/li&gt;
&lt;li&gt;When partial data is acceptable&lt;/li&gt;
&lt;li&gt;When the entire query should be considered failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the balances example above, that decision is intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Balances are required&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If fetching balances fails, the UI cannot function — the mapper fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prices are optional&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If price fetching fails, we still render balances, but we show &lt;code&gt;totalValueUsd&lt;/code&gt; as unavailable and expose the error for the UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the key difference versus hook-heavy composition:&lt;br&gt;&lt;br&gt;
the UI no longer needs to re-implement “what is allowed to compute right now?” on every screen.&lt;/p&gt;

&lt;p&gt;Also, remember: this mapper is not an “ultimate solution.” You should still separate concerns as much as possible.&lt;br&gt;&lt;br&gt;
But when you &lt;em&gt;do&lt;/em&gt; need to merge data, a mapper is the right place to make explicit decisions about partial failure and full failure.&lt;/p&gt;

&lt;p&gt;And don’t be afraid to reuse the same cache with different mappers. If &lt;code&gt;tokenPricesMapper&lt;/code&gt; is already caching prices, a prices-only view can reuse it without refetching or duplicating logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  The &lt;code&gt;safeCall&lt;/code&gt; Wrapper
&lt;/h2&gt;

&lt;p&gt;To support partial failures, we use a small utility:&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SafeResult&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="na"&gt;data&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="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&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;safeCall&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;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SafeResult&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;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;fn&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&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 prevents errors from being thrown while still exposing them to the caller.&lt;/p&gt;

&lt;p&gt;Your UI can now say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here are your balances, but I couldn’t fetch price data because of this error.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s a &lt;strong&gt;first-class, explicit state&lt;/strong&gt;, not an accidental side effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Reuse and Separation of Concerns
&lt;/h2&gt;

&lt;p&gt;You might be wondering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What if I want to display token prices even if I can’t fetch balances?”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;ETH = $30k&lt;/li&gt;
&lt;li&gt;BTC = $90k&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the beauty of this approach — &lt;strong&gt;we’re already calling &lt;code&gt;tokenPricesMapper&lt;/code&gt;, which caches pricing results&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A prices-only screen or widget can reuse the same cached data without duplicating logic or triggering additional requests.&lt;/p&gt;

&lt;p&gt;This separation of concerns also gives us &lt;strong&gt;clean and predictable cache invalidation&lt;/strong&gt;, but more on that in a later post.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison: A Hook-Heavy Approach
&lt;/h2&gt;

&lt;p&gt;Now let’s implement the same feature using a more traditional, hook-heavy pattern.&lt;/p&gt;

&lt;p&gt;We’ll pretend we have standard hooks that internally call plain fetch functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useBalances()&lt;/code&gt; → &lt;code&gt;balanceOf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTokenSymbols()&lt;/code&gt; → &lt;code&gt;symbol&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTokenDecimals()&lt;/code&gt; → &lt;code&gt;decimals&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTokenPrices()&lt;/code&gt; → price API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No mappers involved.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useDisplayBalancesWithHooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimulationSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UseDisplayBalancesResult&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;simulateLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulatePriceError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulateRpcError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;account&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;balancesQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBalances&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulateRpcError&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;symbolsQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTokenSymbols&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulateRpcError&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;decimalsQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTokenDecimals&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulateRpcError&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;pricesQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTokenPrices&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simulatePriceError&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;isBalancesLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;simulateLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;balancesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;symbolsQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;decimalsQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isBalancesError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balancesQuery&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="o"&gt;||&lt;/span&gt;
    &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbolsQuery&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="o"&gt;||&lt;/span&gt;
    &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decimalsQuery&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPriceLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPriceError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pricesQuery&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priceError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;balances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(():&lt;/span&gt; &lt;span class="nx"&gt;DisplayBalance&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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;rawBalance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;balancesQuery&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;index&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;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;symbolsQuery&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;index&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;decimals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decimalsQuery&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;index&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;rawBalance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;decimals&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;rawBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;balanceFormatted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatBigIntToViewTokenAmount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;bigIntValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rawBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;decimals&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;DisplayBalance&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balancesQuery&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;symbolsQuery&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;decimalsQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalValueUsd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isBalancesLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isBalancesError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&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;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pricesQuery&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;calculateTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pricesQuery&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="nx"&gt;isBalancesLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isBalancesError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pricesQuery&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;totalValueUsd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isBalancesLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isBalancesError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isPriceLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isPriceError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;priceError&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’s Actually Happening Here
&lt;/h3&gt;

&lt;p&gt;Most of this hook is not business logic — it’s &lt;strong&gt;coordination logic&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merging multiple async sources&lt;/li&gt;
&lt;li&gt;Defining what “loading” means across queries&lt;/li&gt;
&lt;li&gt;Deciding which errors are fatal and which are optional&lt;/li&gt;
&lt;li&gt;Preventing UI glitches&lt;/li&gt;
&lt;li&gt;Aligning data by index across hooks&lt;/li&gt;
&lt;li&gt;Figuring out &lt;strong&gt;when it’s safe to compute derived values&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, a hook-heavy approach forces you to repeatedly answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What is the app allowed to calculate right now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that logic tends to leak into every screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;If you prefer seeing these ideas in action instead of just reading about them, I’ve put together a small interactive demo that showcases the patterns discussed in this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://react-clean-code-tutorials.vercel.app/" rel="noopener noreferrer"&gt;https://react-clean-code-tutorials.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/WingsDevelopment/react-clean-code-tutorials" rel="noopener noreferrer"&gt;https://github.com/WingsDevelopment/react-clean-code-tutorials&lt;/a&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Fetching from multiple async sources is the &lt;em&gt;real&lt;/em&gt; complexity in DeFi front ends.&lt;/li&gt;
&lt;li&gt;Hook-heavy solutions push orchestration logic into UI layers.&lt;/li&gt;
&lt;li&gt;Mapper-based approaches make data fetching &lt;strong&gt;atomic&lt;/strong&gt;, &lt;strong&gt;predictable&lt;/strong&gt;, and &lt;strong&gt;testable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Partial failures become explicit, not accidental.&lt;/li&gt;
&lt;li&gt;Cache reuse and invalidation become easier by design.&lt;/li&gt;
&lt;li&gt;UI focuses on rendering, not coordination.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;Planned follow-up posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;useQueries vs Promise.all&lt;/strong&gt;: Understanding the Differences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selectors vs Mappers&lt;/strong&gt;: Understanding the Trade-offs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper Data Mapping&lt;/strong&gt;: Format Once, Display Many Variations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect Caching Mechanism&lt;/strong&gt;: Saving Money on RPC Calls &lt;em&gt;without&lt;/em&gt; Increasing Development Cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I’d love input from the community&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;What would you like to understand better next?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching strategies?&lt;/li&gt;
&lt;li&gt;error handling?&lt;/li&gt;
&lt;li&gt;testability?&lt;/li&gt;
&lt;li&gt;performance trade-offs?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know — the next posts will be driven by that.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>architecture</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>The Architecture Mistakes That Slowly Kills Large React Projects</title>
      <dc:creator>WingsDevelopment</dc:creator>
      <pubDate>Sat, 27 Dec 2025 10:38:01 +0000</pubDate>
      <link>https://dev.to/92srdjan/the-architecture-mistakes-that-slowly-kills-large-react-projects-hii</link>
      <guid>https://dev.to/92srdjan/the-architecture-mistakes-that-slowly-kills-large-react-projects-hii</guid>
      <description>&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too much data on the client&lt;/li&gt;
&lt;li&gt;Overuse of &lt;code&gt;useMemo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;React Query becomes too complex for on-chain fetching&lt;/li&gt;
&lt;li&gt;Unpredictable re-renders&lt;/li&gt;
&lt;li&gt;Hard-to-maintain hook logic&lt;/li&gt;
&lt;li&gt;Abstractions created too early that can’t evolve with the project&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Modern DeFi front-ends try to provide &lt;em&gt;maximum transparency&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;everything fetched on-chain, everything computed live, everything reactive.&lt;/p&gt;

&lt;p&gt;This works beautifully at the beginning.&lt;/p&gt;

&lt;p&gt;You have a clean UI, a few contract calls, a couple of hooks, and everything feels manageable.&lt;/p&gt;

&lt;p&gt;But as the project grows, so does the data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more vaults&lt;/li&gt;
&lt;li&gt;more markets&lt;/li&gt;
&lt;li&gt;more balances&lt;/li&gt;
&lt;li&gt;more derived state&lt;/li&gt;
&lt;li&gt;more APYs&lt;/li&gt;
&lt;li&gt;more user positions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly your front-end is doing the job of a &lt;strong&gt;backend + database&lt;/strong&gt;, but inside React components.&lt;/p&gt;

&lt;p&gt;Small mistakes from the early days, a helper hook here, an abstraction there — start to pile up.&lt;/p&gt;

&lt;p&gt;More conditions, more memos, more state.&lt;/p&gt;

&lt;p&gt;Before you notice, you're fighting complexity you never intended to create.&lt;/p&gt;

&lt;p&gt;This post explains &lt;em&gt;why this happens&lt;/em&gt;, &lt;em&gt;why React Query becomes extremely tricky in web3&lt;/em&gt;, and how a simple architecture, &lt;strong&gt;fetch → mapper → hook&lt;/strong&gt; — solves these problems permanently.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Too Much Data on the Client&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;As our datasets grew, we realized not everything needs to be fetched live from the chain. &lt;/p&gt;

&lt;p&gt;Some data is &lt;strong&gt;expensive to compute&lt;/strong&gt;, &lt;strong&gt;rarely changes&lt;/strong&gt;, or is &lt;strong&gt;aggregated from many sources&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;So we shifted specific classes of data to a &lt;strong&gt;backend&lt;/strong&gt; (or subgraph/task worker), then consumed it with the Fetch → Mapper → UI pattern.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But Isn’t This Pattern Less Useful If You Already Have a Backend?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might argue that the Fetch → Mapper → UI architecture becomes less necessary once you introduce a backend.&lt;/p&gt;

&lt;p&gt;But here’s the reality:&lt;/p&gt;

&lt;p&gt;Even with a strong backend, &lt;strong&gt;you will always have data that must come directly from the chain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These values are &lt;strong&gt;too time-sensitive&lt;/strong&gt;, &lt;strong&gt;user-specific&lt;/strong&gt;, or &lt;strong&gt;transaction-gating&lt;/strong&gt; to safely offload to a backend.&lt;/p&gt;

&lt;p&gt;This creates an unavoidable challenge:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your front-end must merge two different worlds:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend-provided data&lt;/strong&gt; (cached, aggregated, slow-changing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-chain data&lt;/strong&gt; (live, reactive, per-user)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Fetch → Mapper → UI pattern is exactly what makes this possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;fetchers&lt;/strong&gt; isolate &lt;em&gt;how&lt;/em&gt; and &lt;em&gt;where&lt;/em&gt; the data comes from (BE or chain).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;mappers&lt;/strong&gt; combine, normalize, format, and reconcile those two sources.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;UI&lt;/strong&gt; receives a single, clean, stable data object — without knowing (or caring) whether it came from RPC, BE, or both.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Overusing &lt;code&gt;useMemo&lt;/code&gt;&lt;/strong&gt;
&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%2Fbwpbzl79n9r7qaa60lx9.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%2Fbwpbzl79n9r7qaa60lx9.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a traditional app, the backend prepares data for you.&lt;/p&gt;

&lt;p&gt;In web3, the blockchain gives you raw, primitive state, you must compute everything yourself.&lt;/p&gt;

&lt;p&gt;More data → more derived calculations → more &lt;code&gt;useMemo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While having only 10 vaults, even if you have 20 memos per vault, its not a big issue, but once number of vaults grow..&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;10 vaults → 200 memos&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;500 vaults → 10k memos!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every re-render triggers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependency comparisons&lt;/li&gt;
&lt;li&gt;recalculations&lt;/li&gt;
&lt;li&gt;diffing&lt;/li&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;li&gt;race conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As data grows, the number of “safe mistakes” drops to zero.&lt;/p&gt;

&lt;p&gt;A single unstable dependency crashes performance.&lt;/p&gt;

&lt;h1&gt;
  
  
  React query is too hard in web3 for on-chain fetching
&lt;/h1&gt;

&lt;p&gt;I have reviewed a lot of projects in DeFi and many of us tried to use &lt;code&gt;useQuery&lt;/code&gt; like in web2 application, making a huge mistake. &lt;/p&gt;

&lt;p&gt;You might wonder why does it matter for &lt;code&gt;react-query&lt;/code&gt; if I am building web2 or web3 application, i am just making RPC calls instead of a HTTP right? Well its not that simple.&lt;/p&gt;

&lt;p&gt;In web2 you have database, or BE service, that is calculating, formatting, joining the data and you get 1 hook - 1 query - everything you need.&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%2Fmnn7redn1phvgeh59qqp.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%2Fmnn7redn1phvgeh59qqp.png" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While in web3 you have only chain, that is much more primitive, and you have to do what would DB do for you in FE, so you end up having situation that looks more like this:&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%2F6qr48z8jhxj0yz5yenje.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%2F6qr48z8jhxj0yz5yenje.png" alt=" " width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you might think, its not a big deal, &lt;/p&gt;

&lt;p&gt;You start by thinking, &lt;em&gt;“I’ll just add an &lt;code&gt;if&lt;/code&gt; in the &lt;code&gt;enabled&lt;/code&gt; field so it fetches only when needed.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then you add some custom refetch logic to the &lt;code&gt;queryKeys&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then you calculate combined loading and error states across multiple &lt;code&gt;useQuery&lt;/code&gt; hooks.&lt;/p&gt;

&lt;p&gt;Maybe you even sprinkle in a little caching or a manual refetch call “just to handle an edge case.”&lt;/p&gt;

&lt;p&gt;And before you realize it, you’ve built &lt;strong&gt;hooks on top of hooks&lt;/strong&gt;, hundreds of lines deep — full of branching conditions, complex dependency rules, and no straightforward &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, or loops. It becomes hard to follow, hard to debug, and even harder to maintain.&lt;/p&gt;

&lt;p&gt;Take a look at simple but still confusing hook-heavy approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt; &lt;span class="c1"&gt;// 1: Fetch vault&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isVaultLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useVault&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;refetchOnMount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// 2: Fetch Net Asset Value (depends on vault)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;netAssetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isNetAssetValueLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNetAssetValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;accountAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                 &lt;span class="c1"&gt;// enabled #1&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// 3: Fetch APY (also depends on vault)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isApyLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useApy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                 &lt;span class="c1"&gt;// enabled #2&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Global loading state becomes:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;isVaultLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nx"&gt;isNetAssetValueLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nx"&gt;isApyLoading&lt;/span&gt;

&lt;span class="c1"&gt;// todo errors.. and other things..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also means that for every data point, we now have not only the value itself but an entire bundle of React Query state attached to it - loading, error, status flags, fetch timestamps, and more.&lt;/p&gt;

&lt;p&gt;And because most of these hooks depend on each other, the entire composite hook becomes “loading” if &lt;strong&gt;any&lt;/strong&gt; of the underlying queries are loading. In practice, you need all the data anyway, so the multiple &lt;code&gt;useQuery&lt;/code&gt; calls end up serving only one purpose: caching.&lt;/p&gt;

&lt;p&gt;Which leads to an obvious question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not simply fetch everything together instead?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example mapper &lt;/span&gt;
&lt;span class="c1"&gt;// 1. Fetch the vault (required) and await for it!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vault&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;fetchVault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// clear failure path, no helper flags in React&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;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vault not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Fetch dependent values in parallel&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;netAssetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchNetAssetValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;accountAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchApy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vaultAddress&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// easily add more in a single line!&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Return fully composed result&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;netAssetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much easier to read!&lt;/p&gt;

&lt;p&gt;After fetching everything in mapper, you simply wrap the entire operation in a single &lt;code&gt;useQuery&lt;/code&gt;. This gives you reactivity, loading and pending states, error handling, and all the other benefits of React Query - without scattering logic across multiple hooks.&lt;/p&gt;

&lt;p&gt;Much easier to read, maintain, and reason about, right? In my opinion: &lt;strong&gt;absolutely yes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the next question is that you might be thinking of is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What about caching?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where things get interesting.&lt;/p&gt;

&lt;p&gt;React Query also provides a &lt;strong&gt;non-hook&lt;/strong&gt; API through the &lt;code&gt;queryClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When I discovered &lt;code&gt;queryClient.fetchQuery&lt;/code&gt; for the first time, it completely changed the way I approached data fetching. You can access the same query client you initialized at the root of your project and call &lt;code&gt;.fetchQuery()&lt;/code&gt; anywhere - with full support for &lt;code&gt;staleTime&lt;/code&gt;, &lt;code&gt;refetchOnWindowFocus&lt;/code&gt;, &lt;code&gt;retries&lt;/code&gt;, and all other React Query options.&lt;/p&gt;

&lt;p&gt;Here’s a simple example of a fetch function that retrieves the owner of a vault and caches it for 30 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getOwnerQueryOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&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="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// readContractQueryOptions utils from wagmi&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;readContractQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getWagmiConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eulerEarnAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&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="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- cache smallest part of the data&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&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;fetchOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&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;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;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- fetch query from query client&lt;/span&gt;
    &lt;span class="nf"&gt;getOwnerQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&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;result&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 means that whenever you use this fetch function inside a higher-level operation, the call is already cached - no need to define caching logic anywhere else.&lt;/p&gt;

&lt;p&gt;With this pattern in place, the large, complicated hook I showed earlier has no reason to call hooks inside hooks anymore. It can simply call plain fetch functions.&lt;/p&gt;

&lt;p&gt;Now imagine the requirements change.&lt;/p&gt;

&lt;p&gt;For example, before fetching anything, you now need to check whether a vault is verified and throw an error if it isn’t.&lt;/p&gt;

&lt;p&gt;In the old hook-based approach, you would need to update every internal hook, add &lt;code&gt;enabled&lt;/code&gt; conditions, and touch ~10 different places.&lt;/p&gt;

&lt;p&gt;With plain fetch functions, the change becomes trivial - you just write something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isVerified&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;fetchIsVerified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debt&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;vault&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;isVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="nx"&gt;Vault&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- you can use if! &lt;/span&gt;
&lt;span class="c1"&gt;// … rest of the code here…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also start running into issues when calculating the final state. &lt;/p&gt;

&lt;p&gt;Maybe you weren’t handling certain error cases before and now you need to. &lt;/p&gt;

&lt;p&gt;Maybe you need to understand exactly when individual queries are refetching. &lt;/p&gt;

&lt;p&gt;With the hook-heavy approach, this becomes complicated quickly, and a lot of unexpected behaviors can occur.&lt;/p&gt;

&lt;h1&gt;
  
  
  Unpredictable re-renders
&lt;/h1&gt;

&lt;p&gt;This problem naturally emerges from the previous issues.&lt;/p&gt;

&lt;p&gt;When your data layer becomes too reactive and too tightly coupled to multiple hooks, the UI starts re-rendering in ways that feel random or impossible to control.&lt;/p&gt;

&lt;p&gt;The first instinct is always the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Let’s just wrap it in useMemo.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But &lt;code&gt;useMemo&lt;/code&gt; doesn’t eliminate complexity — it adds more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;another dependency array&lt;/li&gt;
&lt;li&gt;another equality check&lt;/li&gt;
&lt;li&gt;more memory&lt;/li&gt;
&lt;li&gt;another place things can go wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of stabilizing your UI, you now have yet another reactive layer to manage.&lt;/p&gt;

&lt;p&gt;Let’s do some math.&lt;/p&gt;

&lt;p&gt;Imagine a hook that internally uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 different &lt;code&gt;useQuery&lt;/code&gt; calls → &lt;strong&gt;7 query keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;several formatting or sorting operations wrapped in &lt;code&gt;useMemo&lt;/code&gt; → &lt;strong&gt;3–5 more dependency arrays&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re now maintaining &lt;strong&gt;10+ separate dependency arrays&lt;/strong&gt;, all of which React must compare &lt;strong&gt;on every render&lt;/strong&gt;, for every feature that uses this hook.&lt;/p&gt;

&lt;p&gt;As the app scales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the number of dependency arrays increases&lt;/li&gt;
&lt;li&gt;the number of query keys explodes&lt;/li&gt;
&lt;li&gt;one unstable reference triggers a cascade&lt;/li&gt;
&lt;li&gt;the UI becomes harder to predict&lt;/li&gt;
&lt;li&gt;small mistakes cause large re-render storms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if every dependency is “correct,” React still needs to store all these arrays in memory and compare them constantly. Multiply this across dozens of components and the cost becomes significant.&lt;/p&gt;

&lt;p&gt;This is why “just add more &lt;code&gt;useMemo&lt;/code&gt;” is not a performance strategy — it’s a sign the architecture needs to be simplified.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fetch - mapper - hook layers for getting the data
&lt;/h1&gt;

&lt;p&gt;Moving away from the way we fetched data helped us get rid of all of this things as well, we introduced 3 layers when fetching data, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch functions (which we already covered in fetchOwner example,&lt;/li&gt;
&lt;li&gt;mappers layer before useQuery that calls as many fetch function&lt;/li&gt;
&lt;li&gt;and at the end useQuery to wrap this mapper and get reactivity for UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already partially covered why do we need this, but if you are interested to see diagram and more details about this approach maybe you can check out &lt;a href="https://dev.to/92srdjan/the-web2-mental-model-doesnt-work-in-web3-18on"&gt;THIS BLOG&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating abstractions in early stage that can’t support project evolution
&lt;/h1&gt;

&lt;p&gt;This is a mistake nearly every team makes at some point, myself included. In the early stages of a project, you often don’t fully understand the real data flows, the real scale, or how features will evolve. &lt;/p&gt;

&lt;p&gt;You create abstractions with the best intentions: to be clean, reusable, “future-proof.” But the truth is that early abstractions usually aren’t future-proof at all. They’re built on assumptions that later turn out to be wrong.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;React Query is an amazing library, but web3 is fundamentally different from web2. You can’t treat on-chain RPC calls the same way you treat HTTP endpoints. When you try to, you end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Massive nested hooks&lt;/li&gt;
&lt;li&gt;Too many queries&lt;/li&gt;
&lt;li&gt;Broken loading/error states&lt;/li&gt;
&lt;li&gt;useMemo everywhere&lt;/li&gt;
&lt;li&gt;Unpredictable renders&lt;/li&gt;
&lt;li&gt;Complex dependency arrays&lt;/li&gt;
&lt;li&gt;Hooks that grow to 500–800 lines before anyone notices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By moving fetching out of hooks, simplifying the pipeline, and letting &lt;code&gt;Promise.all&lt;/code&gt; do the heavy lifting—while still using React Query for reactivity—you get a far cleaner architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Debuggable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predictable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to maintain&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly:&lt;/p&gt;

&lt;p&gt;Your front-end stops fighting the blockchain - and starts working with it.&lt;/p&gt;




</description>
    </item>
    <item>
      <title>The Web2 Mental Model Doesn’t Work in Web3</title>
      <dc:creator>WingsDevelopment</dc:creator>
      <pubDate>Sat, 27 Dec 2025 10:31:05 +0000</pubDate>
      <link>https://dev.to/92srdjan/the-web2-mental-model-doesnt-work-in-web3-18on</link>
      <guid>https://dev.to/92srdjan/the-web2-mental-model-doesnt-work-in-web3-18on</guid>
      <description>&lt;p&gt;The Right Way To Fetch On-Chain Data&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Goals of this Post&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Developer friendliness&lt;/li&gt;
&lt;li&gt;Performance improvements&lt;/li&gt;
&lt;li&gt;Simpler code maintenance&lt;/li&gt;
&lt;li&gt;Smarter caching&lt;/li&gt;
&lt;li&gt;Easy, predictable data invalidation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also the goal here is &lt;em&gt;not&lt;/em&gt; to build the perfect, all-encompassing abstraction that works for everyone.&lt;/p&gt;

&lt;p&gt;The goal is to create a &lt;strong&gt;simple, scalable baseline&lt;/strong&gt; that any developer can understand, adopt quickly, and extend as their project grows.&lt;/p&gt;

&lt;p&gt;React Query completely changed the way many of us think about data and state in React. It’s an incredible library - but in web3, a lot of us (myself included) were unintentionally using it the wrong way.&lt;/p&gt;

&lt;p&gt;Let’s explore why.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Web2 Mental Model Doesn’t Work in Web3&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;In a typical web2 front-end, you fetch well-prepared data from a backend or database - a single source of truth:&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%2Froqru0ytugepfu3xilyq.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%2Froqru0ytugepfu3xilyq.png" alt=" " width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s clean. One request → one response → one &lt;code&gt;useQuery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But in web3, your "backend" is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple contracts&lt;/li&gt;
&lt;li&gt;multiple RPC endpoints&lt;/li&gt;
&lt;li&gt;multiple formats&lt;/li&gt;
&lt;li&gt;sometimes a subgraph&lt;/li&gt;
&lt;li&gt;and no server to aggregate or prepare data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the exact same UI ends up looking more like this:&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%2Fi445a7bztrl5s4e59oxg.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%2Fi445a7bztrl5s4e59oxg.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is where things go wrong.&lt;/p&gt;

&lt;p&gt;Fetching data from 3 contracts?&lt;/p&gt;

&lt;p&gt;You now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 different hooks&lt;/li&gt;
&lt;li&gt;3 sets of &lt;code&gt;enabled&lt;/code&gt; logic&lt;/li&gt;
&lt;li&gt;3 query keys&lt;/li&gt;
&lt;li&gt;3 loading/error states&lt;/li&gt;
&lt;li&gt;memoized transformations on top&lt;/li&gt;
&lt;li&gt;selectors&lt;/li&gt;
&lt;li&gt;sorting&lt;/li&gt;
&lt;li&gt;re-renders caused by unstable references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It becomes too complex. Too reactive. Too hard to maintain.&lt;/p&gt;

&lt;p&gt;A single hook grows to 500+ lines of conditions and nested hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So what do we do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We go back to what works.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;A Simpler Model: Fetch → Mapper → UI&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;We borrow inspiration from backend architecture — where systems typically have three layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Presentation&lt;/li&gt;
&lt;li&gt;Services&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For web3 front-ends, the equivalent becomes:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;UI → Mappers → Fetchers&lt;/strong&gt;
&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%2Fejumilbxkm68n6fhypbr.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%2Fejumilbxkm68n6fhypbr.png" alt=" " width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This model restores predictability, improves performance, simplifies debugging, and keeps caching under control.&lt;/p&gt;

&lt;p&gt;Let’s break it down.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Layer 1 — Fetchers (RPC/Network + Cache)&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The fetch layer is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing the actual RPC / HTTP call&lt;/li&gt;
&lt;li&gt;Caching it using &lt;strong&gt;React Query’s non-hook API&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nothing else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the key insight:&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;do not&lt;/strong&gt; use hooks in this layer. Get queryClient directly instead of &lt;code&gt;useQueryClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React Query still handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;staleTime&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;background updates&lt;/li&gt;
&lt;li&gt;and all other non reactive aspects from &lt;code&gt;fetchQuery&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But without introducing reactivity where it doesn’t belong.&lt;/p&gt;

&lt;p&gt;Take a look at the example of how to fetch decimals, balance and use mapper to map those values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTokenDecimalsQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;readContractQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getWagmiConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;erc20Abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decimals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&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="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchTokenDecimals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&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="nf"&gt;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getTokenDecimalsQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&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;^ Here, the fetcher caches the token decimals forever.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTokenBalanceQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;readContractQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getWagmiConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;erc20Abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balanceOf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1 minute&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&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;fetchTokenBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="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="nf"&gt;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;getTokenBalanceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&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;^ Here, the fetcher caches the balance for 1 minute. &lt;/p&gt;

&lt;p&gt;No matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which mapper calls it&lt;/li&gt;
&lt;li&gt;how many screens use it&lt;/li&gt;
&lt;li&gt;how many components reference it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This RPC call &lt;code&gt;balanceOf&lt;/code&gt; only happens once every 1 minute at most. (Or only once in &lt;code&gt;decimals&lt;/code&gt; case)&lt;/p&gt;

&lt;p&gt;Your UI stays fast, your app stays stable, and caching stays centralized.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Layer 2 — Mappers (Business Logic &amp;amp; Data Shaping)&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The mapper layer is where everything comes together.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;understands what the UI needs&lt;/li&gt;
&lt;li&gt;understands how the chain/API supplies data&lt;/li&gt;
&lt;li&gt;calls multiple fetchers&lt;/li&gt;
&lt;li&gt;formats and derives values&lt;/li&gt;
&lt;li&gt;combines data into a clean, ready-to-use structure&lt;/li&gt;
&lt;li&gt;uses plain JS (if/else, map, for, switch, early returns)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because fetchers are non-hook functions, &lt;strong&gt;mappers are also non-hook functions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So you can use normal control flow without React’s limitations.&lt;/p&gt;

&lt;p&gt;This is where complexity shrinks by 10x.&lt;/p&gt;

&lt;p&gt;Here is the example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatBigIntToViewNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ViewNumber&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;react-display-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&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;displayBalanceMapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ViewNumber&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch metadata (forever cache) + balance (1m cache) in parallel&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;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawBalance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetchTokenDecimals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// usually hits the cache (only first time doesn't)&lt;/span&gt;
    &lt;span class="nf"&gt;fetchTokenSymbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// usually hits the cache (only first time doesn't)&lt;/span&gt;
    &lt;span class="nf"&gt;fetchTokenBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// fresh every minute&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="c1"&gt;// format (prepare) your value for view, don't let raw values go any further&lt;/span&gt;
      &lt;span class="na"&gt;balanceFormatted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatBigIntToViewNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&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;Mappers become the &lt;strong&gt;business logic brain&lt;/strong&gt; of your project.&lt;/p&gt;

&lt;p&gt;This mapper is simple and clean, &lt;br&gt;
compare it with "hook-heavy" approach, imagine you try to build anything more complex using the traditional “hook-heavy” pattern - with a separate &lt;code&gt;useQuery&lt;/code&gt; for every tiny data point - things quickly spiral out of control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple &lt;code&gt;enabled&lt;/code&gt; conditions&lt;/li&gt;
&lt;li&gt;multiple loading/error states&lt;/li&gt;
&lt;li&gt;nested hooks&lt;/li&gt;
&lt;li&gt;deeply branching logic&lt;/li&gt;
&lt;li&gt;no clean way to &lt;code&gt;return emptyState&lt;/code&gt; early&lt;/li&gt;
&lt;li&gt;no way to &lt;code&gt;throw error&lt;/code&gt; early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worse, the moment you need to fetch data for anything dynamic - lists of tokens, vaults, markets, positions, etc. — the hook-heavy approach falls apart. Mapping over an array now requires &lt;code&gt;useQueries&lt;/code&gt;, which introduces even more reactivity, more conditions, and still doesn’t solve conditional fetching cleanly.&lt;/p&gt;

&lt;p&gt;No easy gold old &lt;code&gt;for&lt;/code&gt; loop to fetch things!&lt;/p&gt;

&lt;p&gt;You need sortable data for table? Not a problem lets just create &lt;code&gt;displayBalancesMapper&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&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;displayBalancesMapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DisplayBalanceRow&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;tokens&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// simple empty array return&lt;/span&gt;

  &lt;span class="c1"&gt;// Kick off all token rows in parallel; cache handles duplicates efficiently&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;tokens&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;token&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;displayBalanceMapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&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;// Example: sort descending by normalized amount (for UI tables)&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;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortKey&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortKey&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;rows&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;h1&gt;
  
  
  &lt;strong&gt;Layer 3 — UI (Dumb Components, Smart Data)&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Once data reaches the UI layer, everything is simple again.&lt;/p&gt;

&lt;p&gt;UI components should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;display formatted data&lt;/li&gt;
&lt;li&gt;avoid domain logic&lt;/li&gt;
&lt;li&gt;avoid calling fetchers&lt;/li&gt;
&lt;li&gt;avoid doing chain calculations&lt;/li&gt;
&lt;li&gt;rely entirely on React Query cache and mapper output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI and hook should be "dumb":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDisplayBalanceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="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;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;useDisplayBalance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&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="nf"&gt;displayBalanceMapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// UI-level “hide mapper” cache to reduce re-renders from formatting&lt;/span&gt;
    &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 15s window; tweak 15sec–1min as needed&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;useDisplayBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="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="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getDisplayBalanceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&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;And formatting should already be done long before rendering:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DisplayTokenValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DisplayTokenAmount&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;react-display-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// handels loading, error states but also renders values perfectly&lt;/span&gt;
&lt;span class="c1"&gt;// example: &amp;lt;span customStyleHere&amp;gt;$&amp;lt;/span&amp;gt;&amp;lt;span customStyleHere&amp;gt;34.22&amp;lt;/span&amp;gt; and more..&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DisplayTokenValue&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;rest&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;span class="nx"&gt;balanceFormatted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="c1"&gt;// user sees '$42.22' with option to style $ sign separetely&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, using the number formatting logic from our new library that does heavy lifting and keeping things broad enough so that mapper can satisfy any UI needs:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/WingsDevelopment/react-display-value" rel="noopener noreferrer"&gt;&lt;strong&gt;https://github.com/WingsDevelopment/react-display-value&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This removes the need for &lt;code&gt;useMemo&lt;/code&gt; entirely.&lt;/p&gt;

&lt;p&gt;Every piece of data you render is already:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formatted&lt;/li&gt;
&lt;li&gt;normalized&lt;/li&gt;
&lt;li&gt;rounded&lt;/li&gt;
&lt;li&gt;signed&lt;/li&gt;
&lt;li&gt;symbolized&lt;/li&gt;
&lt;li&gt;display-ready&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your &lt;code&gt;useQuery&lt;/code&gt; in the UI becomes nothing more than a reactive wrapper around a mapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useQuery({
  queryKey: ["something", params],
  queryFn: () =&amp;gt; mapper(params),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No complexity.&lt;br&gt;
No extra fetching.&lt;br&gt;
No nested hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Preventing Unnecessary Re-renders With UI-Level Cache&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s another powerful trick:&lt;/p&gt;

&lt;p&gt;You can “hide” your mapper inside a &lt;code&gt;useQuery&lt;/code&gt; with a short cache window (e.g. 15–60 seconds or more).&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;heavy formatting&lt;/li&gt;
&lt;li&gt;expensive maps&lt;/li&gt;
&lt;li&gt;large number reductions&lt;/li&gt;
&lt;li&gt;symbol/sign parsing&lt;/li&gt;
&lt;li&gt;and other CPU-heavy mapping logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…will run &lt;strong&gt;once every 15–60 seconds or what ever your need is&lt;/strong&gt;, even if the component re-renders multiple times.&lt;/p&gt;

&lt;p&gt;This turns the mapper into a mini “UI cache layer,” which dramatically reduces re-renders and CPU churn in complex dashboards or tables.&lt;/p&gt;

&lt;p&gt;It’s an incredibly simple optimization, but the payoff is huge, especially in data-heavy DeFi applications.&lt;/p&gt;

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

&lt;p&gt;React Query is powerful - but web3 and on-chain data fetching changes how we must use it.&lt;/p&gt;

&lt;p&gt;The traditional approach (lots of hooks, lots of memos, lots of reactivity) breaks down as soon as your application grows beyond a handful of RPC calls.&lt;/p&gt;

&lt;p&gt;By adopting the &lt;strong&gt;Fetch → Mapper → UI&lt;/strong&gt; architecture:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ You isolate complexity
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You centralize caching
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You remove nested hooks
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You avoid re-render storms
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You keep React Query exactly where it belongs
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You get predictable behavior even at scale
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✔ You make your codebase far easier to maintain
&lt;/h3&gt;

&lt;p&gt;This approach doesn’t try to solve everything.&lt;/p&gt;

&lt;p&gt;It gives you a &lt;strong&gt;clean, scalable foundation&lt;/strong&gt; that any teammate can understand, extend, and trust - the opposite of the fragile hook soup most web3 apps end up with.&lt;/p&gt;

&lt;p&gt;In a world where front-ends must behave like backends, this architecture makes the difference between a codebase that collapses under its own weight and a codebase that grows gracefully.&lt;/p&gt;




&lt;h1&gt;
  
  
  More to come
&lt;/h1&gt;

&lt;p&gt;This article set the baseline. Next, I’ll publish short, focused deep-dives that build on the &lt;strong&gt;Fetch → Mapper → UI&lt;/strong&gt; pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Composing multiple sources of data&lt;/li&gt;
&lt;li&gt;Caching strategies beyond - group invalidation, singular invalidation&lt;/li&gt;
&lt;li&gt;Recognize data direction - When &lt;code&gt;useMemo&lt;/code&gt; is actually the right tool&lt;/li&gt;
&lt;li&gt;Error handling &amp;amp; partial data&lt;/li&gt;
&lt;li&gt;Testing &amp;amp; observability&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>react</category>
      <category>tanstackquery</category>
      <category>web3</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
