<?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: Artem Trukhanoff</title>
    <description>The latest articles on DEV Community by Artem Trukhanoff (@artemx9).</description>
    <link>https://dev.to/artemx9</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%2F422858%2F2b84fd2f-9571-4aab-a205-f729444417a2.jpeg</url>
      <title>DEV Community: Artem Trukhanoff</title>
      <link>https://dev.to/artemx9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/artemx9"/>
    <language>en</language>
    <item>
      <title>The Code You Ship Today Will Be Read By Someone Who Wasn't In The Room</title>
      <dc:creator>Artem Trukhanoff</dc:creator>
      <pubDate>Wed, 03 Jun 2026 00:02:20 +0000</pubDate>
      <link>https://dev.to/artemx9/the-code-you-ship-today-will-be-read-by-someone-who-wasnt-in-the-room-57dl</link>
      <guid>https://dev.to/artemx9/the-code-you-ship-today-will-be-read-by-someone-who-wasnt-in-the-room-57dl</guid>
      <description>&lt;p&gt;This is not an article for juniors excited about the latest React ecosystem drop.&lt;/p&gt;

&lt;p&gt;This is for the senior who's inheriting a codebase, the CTO deciding what stack the next five years run on, and the tech lead who has to explain to a new hire why nothing is where they expect it to be.&lt;/p&gt;

&lt;p&gt;The frontend industry has spent the last four years selling "simplicity." What it actually shipped was complexity with better branding — and the bill comes due the moment the original author leaves the room.&lt;/p&gt;

&lt;p&gt;I've been that original author. And I've been the person inheriting the codebase six months later. The experience is very different depending on which tools were used.&lt;/p&gt;

&lt;p&gt;This article came from a mistake I made on my own project. I followed the hype — tried RTK Query, then React Query alongside Redux. At some point I found myself pulling &lt;code&gt;isLoading&lt;/code&gt; from Redux while &lt;code&gt;data&lt;/code&gt; lived in React Query cache. Two systems coordinating inside one component. One updating the other. No single place to look at and understand what was happening. That's when I stopped and asked why I was doing this to myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  When The Owner Changes, The Code Becomes A Pumpkin
&lt;/h2&gt;

&lt;p&gt;Every architectural decision has two phases: the day you write it, and every day after that when someone else has to read it.&lt;/p&gt;

&lt;p&gt;The libraries being pushed hardest right now — RTK Query, TanStack Query, and their associated patterns — are optimized entirely for phase one. Fast to write. Minimal ceremony. Feels clean in a greenfield project with the original author at the keyboard.&lt;/p&gt;

&lt;p&gt;Then someone leaves. Or you come back after six months. Or a new senior joins and spends their first two weeks asking "where does this data actually come from?"&lt;/p&gt;

&lt;p&gt;Now you find out what your architecture is really worth.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;store/gamesState.ts&lt;/code&gt; Is A Contract
&lt;/h2&gt;

&lt;p&gt;Here's what a Redux store file actually is: a &lt;strong&gt;singleton&lt;/strong&gt;. One file. One source of truth. Immutable state, pure transitions. Every piece of state your application can be in is described there, and every action that causes a transition is explicit and named.&lt;/p&gt;

&lt;p&gt;Any developer — regardless of whether they were on the team when it was written — can open &lt;code&gt;store/gamesState.ts&lt;/code&gt; and read it like a specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is not boilerplate. This is documentation.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FETCH_GAMES_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;games&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What caused this state? &lt;code&gt;FETCH_GAMES_SUCCESS&lt;/code&gt;. What changed? Exactly these fields. What was it before? Open DevTools, click the action, read the diff.&lt;/p&gt;

&lt;p&gt;This is not a legacy pattern. This is &lt;strong&gt;explicit state as specification&lt;/strong&gt;. The "boilerplate" everyone complained about was load-bearing. It forced developers to name their intentions, describe their transitions, and put business logic somewhere findable.&lt;/p&gt;

&lt;p&gt;When you replace this with hooks and cache keys, you don't remove the complexity. You distribute it across the component tree where it has no address.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Even Has A Name. Several, Actually.
&lt;/h2&gt;

&lt;p&gt;The architecture I'm describing — thunk + reducer + selector + container + representation component — isn't just a pattern that feels right. It maps directly onto SOLID principles. Not as a coincidence, but because it was designed around the same ideas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Responsibility&lt;/strong&gt; is the most obvious one. Each layer does exactly one thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The thunk handles the API call and hides the HTTP details;&lt;/li&gt;
&lt;li&gt;The reducer owns state transitions — nothing else;&lt;/li&gt;
&lt;li&gt;The selector projects state into exactly what the component needs;&lt;/li&gt;
&lt;li&gt;The container connects the store to the component;&lt;/li&gt;
&lt;li&gt;The representation component renders.
You can open any one of these files and know immediately what it's responsible for. That's not an accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Open/Closed&lt;/strong&gt; follows naturally. You add a new action without touching existing reducers that don't care about it. You add a new selector without modifying any component. You extend the system by adding, not by modifying. The existing, tested code stays untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency Inversion&lt;/strong&gt; is where it gets interesting. The representation component doesn't know where its data comes from — it receives props and renders. The container depends on selectors and dispatch, not on fetch or axios or any specific API implementation. The thunk hides the entire HTTP layer behind an action creator. Every layer depends on abstractions, not on concrete implementations. Swap your HTTP client, change your API shape, refactor your backend response — the components don't care.&lt;/p&gt;

&lt;p&gt;With React Query, the component imports &lt;code&gt;useQuery&lt;/code&gt;, calls a specific query function, and is now directly coupled to the data fetching layer. The dependency arrow points the wrong way, business logic is not encapsulated. SOLID 101.&lt;/p&gt;

&lt;p&gt;None of this is theoretical. These principles exist because codebases need to be maintained by people who weren't there when it was written. Redux + thunk enforces them structurally. React Query leaves them as an exercise for the reader.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Testing Argument Nobody Makes
&lt;/h2&gt;

&lt;p&gt;Want to test every possible UI state your application can render?&lt;/p&gt;

&lt;p&gt;With Redux: create a store factory, inject any state you want, render the component. Done. You can test loading states, error states, empty states, partial states, race conditions — all without a running backend, without mocking fetch, without coordinating multiple hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Test any UI state in three lines&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTestStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;games&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&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="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GameList&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skeleton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same store factory works in Storybook. You want to visually develop a component in a specific state? Pass the store. Every permutation is reproducible, shareable, and documented in your story file.&lt;/p&gt;

&lt;p&gt;This matters most in applications where state transitions carry real business weight — dashboards, e-commerce flows, multi-step purchase funnels. Consider a checkout widget that has to handle: items in cart, discount applied, payment processing, payment failed, retry pending, order confirmed. Each of these is a distinct UI state that a developer needs to build, a designer needs to review, and a QA engineer needs to verify independently.&lt;/p&gt;

&lt;p&gt;With a store factory, each state is a named, reproducible fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createTestStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cartWithDiscount&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;createTestStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentProcessing&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;createTestStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentFailed&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;createTestStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderConfirmed&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer can build and test each stage in isolation without completing an actual purchase. A designer can review every edge case in Storybook without touching a staging environment. QA can write a deterministic test for the "payment failed → retry" transition without timing their assertions around network requests.&lt;/p&gt;

&lt;p&gt;With React Query, you are simulating a sequence of network responses to arrive at a UI state that Redux can simply declare. The test becomes an integration test whether you want it to be or not.&lt;/p&gt;

&lt;p&gt;Can React Query do this? Not cleanly. You're mocking &lt;code&gt;fetch&lt;/code&gt;, intercepting network requests, or wrapping components in &lt;code&gt;QueryClientProvider&lt;/code&gt; with pre-seeded cache — and then praying the cache hydration timing doesn't introduce flakiness. The test setup becomes an archaeology project.&lt;/p&gt;

&lt;p&gt;Moreover, backend data is not UI state. It's a remote resource your UI happens to display. When you treat API responses as state, you couple your component layer to the network contract — and when that contract changes, your UI logic changes with it.&lt;/p&gt;

&lt;p&gt;The store as a factory for UI states is one of the most underrated architectural advantages of Redux, and it's never mentioned in the articles declaring Redux dead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your QA Team Had A Superpower And You Took It Away
&lt;/h2&gt;

&lt;p&gt;Redux DevTools lets you export the complete state of an application at any point in time — every action, in order, with the state diff after each one — as a JSON file.&lt;/p&gt;

&lt;p&gt;When a tester found a bug, they exported the log and sent it to the developer. The developer imported it, time-travelled to the exact moment of failure, and reproduced the bug deterministically. No "I can't reproduce it." No guessing at sequences. No "works on my machine."&lt;/p&gt;

&lt;p&gt;I've worked on teams where QA did exactly this. A tester would export the DevTools log, drop it in Slack, and by the time you opened it you already knew what happened. That's not a workflow you appreciate until it's gone.&lt;/p&gt;

&lt;p&gt;What does your debugging workflow look like with React Query? You have a snapshot of the current cache. No history. No sequence. No "what state was the application in when this happened." When a bug depends on the order of requests, a race condition between a cache invalidation and a component mount, or a stale closure over a query key — you are on your own.&lt;/p&gt;

&lt;p&gt;I've debugged these. They take days, not hours. And every time, the fix was straightforward once you actually understood what happened — which was the hard part.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Caching Myth
&lt;/h2&gt;

&lt;p&gt;The primary selling point of React Query is client-side caching. Fetch the data once, serve it from cache, background-revalidate when stale.&lt;/p&gt;

&lt;p&gt;Here's the thing: your backend already has Redis. Your HTTP layer already has &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, and &lt;code&gt;Last-Modified&lt;/code&gt;. These mechanisms are decades old, battle-tested, and work for every client simultaneously — browser, mobile, service worker, another backend service.&lt;/p&gt;

&lt;p&gt;Client-side caching with React Query is consistent only per-tab, per-user, per-session. Two users looking at the same data can see different things. Two tabs in the same browser can diverge by default — cross-tab sync requires additional configuration. Invalidation is manual and per-endpoint.&lt;/p&gt;

&lt;p&gt;If your backend API response is slow enough that client-side caching measurably improves your application, the correct fix is &lt;code&gt;Cache-Control: max-age=300&lt;/code&gt; on the API response, or a Redis layer in front of the slow query. Both solutions are simpler, more consistent, and cover more clients than a JavaScript caching library.&lt;/p&gt;

&lt;p&gt;Client-side caching in a library is a solution to a backend problem. It became popular because it's easier to install an npm package than to convince your backend team to add cache headers. That's a political solution masquerading as a technical one.&lt;/p&gt;

&lt;p&gt;And if you're hitting a third-party API you don't control — the answer is a backend gateway, not a JavaScript cache. You get Redis, hidden credentials, rate limiting, and response transformation in one place. The frontend still gets clean HTTP headers. The frontend shouldn't know the third-party API exists at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Mental Models Is Not Separation Of Concerns
&lt;/h2&gt;

&lt;p&gt;The recommended pattern for "doing Redux and React Query correctly" is: React Query owns server state, Redux owns client state. Sounds clean. In practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "Clean separation"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedGameId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Redux&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;game&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;                           &lt;span class="c1"&gt;// React Query&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="s1"&gt;game&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedId&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;fetchGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedId&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="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;selectedId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                    &lt;span class="c1"&gt;// coordination between systems&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have two systems that need to know about each other. Redux feeds React Query a key. React Query refetches when the key changes. But what happens when the Redux state updates before the query refetches? What if the component remounts during a background refetch? What if &lt;code&gt;enabled&lt;/code&gt; flips to &lt;code&gt;false&lt;/code&gt; mid-request?&lt;/p&gt;

&lt;p&gt;These are not edge cases. These are the normal conditions of a real application under normal user behavior. And when they go wrong, you are debugging two systems simultaneously with two different DevTools that don't share a timeline.&lt;/p&gt;

&lt;p&gt;This isn't separation of concerns. It's multiplication of cognitive load. You're not choosing the right tool for each job — you're managing a coordination problem you created by splitting one job across two tools.&lt;/p&gt;

&lt;p&gt;One system. One DevTools. One data flow from request to render. The complexity is real either way — the question is whether it's concentrated somewhere readable or distributed somewhere invisible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boilerplate That Describes Business Logic Is A Specification
&lt;/h2&gt;

&lt;p&gt;The core argument against Redux was always the boilerplate. Four files per feature. Action type constants. Explicit reducers for every transition.&lt;/p&gt;

&lt;p&gt;Here's what that argument misses: &lt;strong&gt;boilerplate that describes business logic is not boilerplate. It is a specification.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FETCH_GAMES_REQUEST&lt;/code&gt;, &lt;code&gt;FETCH_GAMES_SUCCESS&lt;/code&gt;, &lt;code&gt;FETCH_GAMES_FAILURE&lt;/code&gt; are not ceremony. They are the named states of a business operation, written once, readable forever. The reducer that handles them is not repetitive noise — it is the documented contract of what your application does with data.&lt;/p&gt;

&lt;p&gt;When you eliminate this in favor of a &lt;code&gt;useQuery&lt;/code&gt; hook that manages its own internal state machine invisibly, you didn't remove the complexity. You moved it inside a library where it has no name, no address, and no audit trail.&lt;/p&gt;

&lt;p&gt;The industry optimized for the experience of writing code. It made writing fast and reading hard. The people who write the code and the people who maintain it are rarely the same people — and they're almost never in the same time zone when the production incident happens at 2 AM.&lt;/p&gt;

&lt;p&gt;I've been on both sides of that 2 AM call.&lt;/p&gt;




&lt;h2&gt;
  
  
  React's Own Premise
&lt;/h2&gt;

&lt;p&gt;There's a principle so fundamental to React that it's easy to forget it's an architectural statement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;UI = f(state)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;React's entire model is built on this. You describe what state is, React renders its representation. The component doesn't decide what to show — it reads state and maps it to UI. That's it.&lt;/p&gt;

&lt;p&gt;This means state should be the first-class citizen of your architecture. Explicit, addressable, testable in isolation from the network, from components, from rendering. The UI is just the output.&lt;/p&gt;

&lt;p&gt;Redux understood this and built the entire model around it. The store is the complete description of what your application is right now. The component reads it and renders. Simple, honest, aligned with how React actually works.&lt;/p&gt;

&lt;p&gt;React Query inverts this. State becomes a derivative of network request history — what was fetched, when, and whether it's stale. You're no longer describing &lt;em&gt;what is&lt;/em&gt;, you're describing &lt;em&gt;where it came from and when it expires&lt;/em&gt;. The UI is now a function of fetch history, not of state.&lt;/p&gt;

&lt;p&gt;When your architecture fights the mental model of the framework you're using, you're paying a tax on every component you write. You just don't see it on the invoice until someone else has to read the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;If you're a CTO choosing a stack: ask yourself which codebase you want to inherit in three years. The one where every state transition is named and traceable, tests are deterministic, QA can export a reproduction case, and any new hire can read the store like documentation — or the one where the data flow lives inside hooks, cache keys, and the institutional memory of developers who have since left.&lt;/p&gt;

&lt;p&gt;If you're a senior evaluating your current architecture: the question isn't which library is more modern. The question is what happens to your application's readability the day the original author is unavailable.&lt;/p&gt;

&lt;p&gt;And if you're thinking "this only matters for large, complex apps" — there's no such thing as a permanently small project. Either it dies, or it grows. When it grows, you pay for every architectural shortcut taken when it was "just a small app."&lt;/p&gt;

&lt;p&gt;Boring, explicit, traceable code outlives clever, minimal, modern code. Every time.&lt;/p&gt;

&lt;p&gt;Redux + thunk + reselect. Four files per feature. Named action types. Explicit reducers. Store factory for tests and Storybook. DevTools for QA. It was never broken.&lt;/p&gt;

&lt;p&gt;The tools that enforce good architecture through friction aren't legacy. They're honest about what software actually costs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Artem Trukhanov is a Lead Frontend Engineer with 10+ years of experience in React, TypeScript, and Node.js. Currently building &lt;a href="https://github.com/ArtemX9/grimoire" rel="noopener noreferrer"&gt;Grimoire&lt;/a&gt; — a self-hosted game backlog manager with Steam, PSN &amp;amp; Xbox sync and AI recommendations. &lt;a href="https://trukhanoff.dev" rel="noopener noreferrer"&gt;trukhanoff.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redux</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
