<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged redux</title>
<link>https://coderlegion.com/tag/redux</link>
<description>Powered by Question2Answer</description>
<item>
<title>Redux Refactoring Playbook: From Action-Types Soup to RTK Query — Without Rewriting the App</title>
<link>https://coderlegion.com/32949/redux-refactoring-playbook-from-action-types-soup-to-rtk-query-without-rewriting-the-app</link>
<description>&lt;p&gt;Every React codebase of a certain age has one: the legacy Redux store. Hundreds of thunk actions, manually managed loading flags, per-feature cache invalidation, and a polling timer per data type pulling the full payload whether anything changed or not.&lt;/p&gt;
&lt;p&gt;Ours lived under a real-time monitoring dashboard tracking 1,000+ GSM installations at Orange Romania — and by the time we migrated to RTK Query, the story ended with dashboard responsiveness up ~40%, telemetry traffic down 20%, and &quot;frozen dashboard&quot; complaints down 30%.&lt;/p&gt;
&lt;p&gt;The previous article told that performance story. This one is the refactoring &lt;em&gt;mechanics&lt;/em&gt;: in what order things moved, which decisions unlocked the next one, and the one rule that made a no-rewrite migration possible.&lt;/p&gt;
&lt;h3 id=&quot;rule-1-strangle-don-t-rewrite&quot;&gt;Rule 1 — Strangle, don't rewrite&lt;/h3&gt;
&lt;p&gt;The store wasn't broken in one place; it was broken &lt;em&gt;everywhere&lt;/em&gt;: a switch-case reducer file nobody fully read, five middleware, &lt;code&gt;isLoading&lt;/code&gt; / &lt;code&gt;isRefreshing&lt;/code&gt; / &lt;code&gt;hasError&lt;/code&gt; flag families per resource, and component-level &lt;code&gt;useEffect&lt;/code&gt; chains firing fetches in a specific order &quot;because it worked.&quot;&lt;/p&gt;
&lt;p&gt;A big-bang rewrite was a non-starter: the dashboard was used every day by operations teams. So the rule was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One page migrates per PR. The rest of the app keeps running on the legacy store untouched.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Redux makes this possible because &lt;code&gt;combineReducers&lt;/code&gt; is natural strangler scaffolding:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const rootReducer = combineReducers({
  // new pages live here
  ordersApi: ordersApi.reducer,          // RTK Query
  invoices: invoicesSlice.reducer,       // RTK slice
  // legacy pages keep their ducks until their turn
  legacyTelemetry: legacyTelemetryReducer, // switch-case reducer, retired last
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each migration PR carried its own before/after render counts and INP p75 from a fixed scripted interaction. If the numbers moved the wrong way, the PR didn't merge — refactoring without a measurement loop is just rewriting with extra steps.&lt;/p&gt;
&lt;p&gt;The code was the easy half. The pitch mattered as much: I sold this as risk control, not cleanup. Every PR shippable alone meant no feature-freeze window that anyone had to buy; the cost I did have to name up front was the parallel-store tax: two stores side by side meant duplicated fixtures and slower reviews while a slice waited for its turn. Leadership signed off because every migration PR ended with a measurement, not with the word &quot;done&quot;. One more org-side lesson: the change-flag contract needed a backend buy-in, and I opened that conversation politely, late. Open it on day one; that delay cost us a month.&lt;/p&gt;
&lt;h3 id=&quot;rule-2-selectors-are-the-api-freeze-them-first-&quot;&gt;Rule 2 — Selectors are the API. Freeze them first.&lt;/h3&gt;
&lt;p&gt;The trick that removed 90% of the migration risk: &lt;strong&gt;components never touched state shapes directly.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The legacy store had selector functions (&lt;code&gt;selectOrders&lt;/code&gt;, &lt;code&gt;selectNodeById&lt;/code&gt;, &lt;code&gt;selectIsNodeActive&lt;/code&gt;). Before migrating anything, we froze them as the contract and then made each selector a re-export shim that could source data from either world:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// selectors/orders.js
import { legacyOrders } from './legacySelectors';
import { useGetOrdersQuery } from '../api/ordersApi';

export function useOrders(selector) {
  const { data, isFetching } = useGetOrdersQuery();
  // legacy pages keep working against the same contract
  return useMemo(() =&amp;gt; selector(data ?? legacyOrders()), [data, selector]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Simplified — the real shims carried fallbacks during the window when a page's data half-lived in the old store. The point is the shape: &lt;strong&gt;the component-facing API never moved, only its plumbing did.&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Result: a &quot;migration PR&quot; changed the data layer under a page, not forty components over it. That made page-by-page migration realistic rather than a nice idea.&lt;/p&gt;
&lt;h3 id=&quot;rule-3-kill-loading-flags-with-rtk-query-not-with-better-thunks&quot;&gt;Rule 3 — Kill loading flags with RTK Query, not with better thunks&lt;/h3&gt;
&lt;p&gt;There was a middle step available: keep the thunks and clean them up. I'd have kept every race. Request A finishing after request B silently overwrote fresher data, and that was our most common production bug, because each flag family carried its own private version of it.&lt;/p&gt;
&lt;p&gt;So server state moved into RTK Query, and components declare what they need, not how to fetch it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before: every call site, every page
dispatch(fetchOrders());
// + component-local: isLoading, ordersError, lastRefreshed...

// after: the component declares the requirement
const { data: orders, isLoading } = useGetOrdersQuery(undefined, {
  pollingInterval: 0, // see Rule 4 — we don't poll blindly
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seventeen loading-flag families disappeared in this step. After it, a component tracking its own fetch lifecycle existed in exactly one place: a page that legitimately needed manual control.&lt;/p&gt;
&lt;h3 id=&quot;rule-4-polling-moves-from-timers-to-tags&quot;&gt;Rule 4 — Polling moves from timers to tags&lt;/h3&gt;
&lt;p&gt;Old polling: one &lt;code&gt;setInterval&lt;/code&gt; per data type, full payload every tick. On a dashboard watching 1,000+ nodes, that's thousands of identical requests an hour and constant re-render churn over data that hadn't changed.&lt;/p&gt;
&lt;p&gt;New polling is no polling — it's a &lt;strong&gt;change-flag endpoint&lt;/strong&gt;: a ten-byte version string the dashbo&lt;br&gt;
ard polls on a timer, and the real data refetches when that string actually changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Nodes'],
  endpoints: (build) =&amp;gt; ({
    nodesChanged: build.query&amp;lt;{ version: string }, void&amp;gt;({
      query: () =&amp;gt; 'nodes/changed',       // returns a version string only
    }),
    getNodes: build.query&amp;lt;NodeState[], void&amp;gt;({
      query: () =&amp;gt; 'nodes',
      providesTags: ['Nodes'],
    }),
    // refetch tagged data only when the flag version changes:
    // a listener middleware polls `nodesChanged` (cheap, ~10 lines) and,
    // on version change, dispatches api.util.invalidateTags(['Nodes'])
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const flagListener = createListenerMiddleware();

flagListener.startListening({
  predicate: (action, currentState, previousState) =&amp;gt; {
    // the version lives in RTK Query's own cache, so the store
    // snapshot gives us both &quot;current&quot; and &quot;what it used to be&quot;
    const cur = nodesChanged.select()(currentState)?.data;
    const prev = nodesChanged.select()(previousState)?.data;
    return cur != null &amp;amp;&amp;amp; cur !== prev;
  },
  effect: (action, api) =&amp;gt; {
    api.dispatch(api.util.invalidateTags(['Nodes']));
  },
});

// the flag endpoint itself: cheap on purpose
//   query: () =&amp;gt; 'nodes/changed',   // returns ~10 bytes: a version string
//   pollingInterval: 5000,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happens on an update, end to end: the version goes into the cache and&lt;br&gt;
&lt;code&gt;predicate&lt;/code&gt; sees it differ from the previous snapshot, &lt;code&gt;invalidateTags&lt;/code&gt; marks&lt;br&gt;
every tagged entry stale &lt;strong&gt;without removing it&lt;/strong&gt; (the screen keeps the old&lt;br&gt;
data), mounted subscribers immediately refetch — deduplicated to one request&lt;br&gt;
per endpoint+args no matter how many components listen — and one re-render&lt;br&gt;
lands the new data. A component sees &lt;code&gt;isLoading: false, isFetching: true&lt;/code&gt;&lt;br&gt;
during that window, which is why nothing flashes. If the flag endpoint dies,&lt;br&gt;
data simply goes stale until the next successful tick; nothing breaks. If&lt;br&gt;
nodes changed five times between two ticks, one refetch covers it: the&lt;br&gt;
version is a snapshot of &quot;what is true now&quot;, not an event queue.&lt;/p&gt;
&lt;h3 id=&quot;what-stayed-in-redux-core&quot;&gt;What stayed in Redux core&lt;/h3&gt;
&lt;p&gt;A mistake we avoided (mostly): RTK Query is for &lt;strong&gt;server state only&lt;/strong&gt;. UI-owned state — selected node, filter panel state, column layout, wizard steps — stayed in plain &lt;code&gt;createSlice&lt;/code&gt;. Migrating that into queries would have replaced a working pattern with a worse one.&lt;/p&gt;
&lt;p&gt;The end state: slices for decisions, RTK Query for data, zero custom cache code, zero hand-rolled loading flags.&lt;/p&gt;
&lt;h3 id=&quot;the-honest-ledger&quot;&gt;The honest ledger&lt;/h3&gt;
&lt;p&gt;From the published case, measured as &lt;em&gt;perceived dashboard responsiveness&lt;/em&gt; (how fast fresh data lands after a change, without UI-thread churn):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dashboard responsiveness: &lt;strong&gt;+~40%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&quot;Frozen dashboard&quot; complaints: &lt;strong&gt;−30%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;REST telemetry traffic: &lt;strong&gt;−20%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I can't give you is a LOC table with a precise count — we deleted more code than we wrote every migration PR, and by the end the store directory had visibly halved; I kept no exact tally, and a made-up precision here would be dishonest.&lt;/p&gt;
&lt;h3 id=&quot;what-i-d-do-differently&quot;&gt;What I'd do differently&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with RTK Query for any new page.&lt;/strong&gt; Half the migration tail was legacy thunks for features that could have been endpoints from day one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Freeze selectors before touching anything.&lt;/strong&gt; The component-facing API is the migration's unit of safety.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure per PR, not per epic.&lt;/strong&gt; The scripted interaction + INP check cost minutes per PR and caught two regressions a &quot;it looks fine&quot; test never would.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the change-flag backend contract early.&lt;/strong&gt; The polling redesign was the single biggest win and it needed a backend buy-in — politeness about it cost us a month.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;your-refactoring-checklist&quot;&gt;Your refactoring checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Inventory actions/thunks/loading flags — count them, this is your &quot;before&quot; baseline.&lt;/li&gt;
&lt;li&gt;Freeze selectors as the component-facing contract.&lt;/li&gt;
&lt;li&gt;Add RTK Query via &lt;code&gt;combineReducers&lt;/code&gt; alongside the legacy store.&lt;/li&gt;
&lt;li&gt;Migrate one page per PR; measure INP p75 + render count on a fixed interaction.&lt;/li&gt;
&lt;li&gt;Server state → endpoints with tags; UI state stays in slices.&lt;/li&gt;
&lt;li&gt;Replace timers with a change-flag contract before touching fetch logic.&lt;/li&gt;
&lt;li&gt;Delete: action-type strings, flag families, custom cache code. The deletion count is the score.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The performance numbers come from the same production system as my earlier piece, &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/veaceslav/a-40-faster-fault-detection-pipeline-without-websockets-the-polling-trick-we-shipped-e28&quot;&gt;A 40% Faster Fault-Detection Pipeline Without WebSockets&lt;/a&gt; — that one tells the polling-protocol story; this is the data-layer refactoring around it.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32949/redux-refactoring-playbook-from-action-types-soup-to-rtk-query-without-rewriting-the-app</guid>
<pubDate>Thu, 29 Oct 2026 08:51:02 +0000</pubDate>
</item>
<item>
<title>The Code You Ship Today Will Be Read By Someone Who Wasn't In The Room</title>
<link>https://coderlegion.com/21088/the-code-you-ship-today-will-be-read-by-someone-who-wasnt-in-the-room</link>
<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 &quot;simplicity.&quot; 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;hr&gt;
&lt;h3 id=&quot;when-the-owner-changes-the-code-becomes-a-pumpkin&quot;&gt;When The Owner Changes, The Code Becomes A Pumpkin&lt;/h3&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 &quot;where does this data actually come from?&quot;&lt;/p&gt;
&lt;p&gt;Now you find out what your architecture is really worth.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;store-gamesstate-ts-is-a-contract&quot;&gt;&lt;code&gt;store/gamesState.ts&lt;/code&gt; Is A Contract&lt;/h3&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// This is not boilerplate. This is documentation.
case FETCH_GAMES_SUCCESS:
  return {
    ...state,
    items: action.payload.games,
    total: action.payload.total,
    isLoading: false,
  };
&lt;/code&gt;&lt;/pre&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 &quot;boilerplate&quot; 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;hr&gt;
&lt;h3 id=&quot;it-even-has-a-name-several-actually-&quot;&gt;It Even Has A Name. Several, Actually.&lt;/h3&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.&lt;br&gt;
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;hr&gt;
&lt;h3 id=&quot;the-testing-argument-nobody-makes&quot;&gt;The Testing Argument Nobody Makes&lt;/h3&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Test any UI state in three lines
const store = createTestStore({ games: { items: [], isLoading: true } });
render(&amp;lt;GameList /&amp;gt;, { store });
expect(screen.getByTestId('skeleton')).toBeInTheDocument();
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;createTestStore({ checkout: cartWithDiscount });
createTestStore({ checkout: paymentProcessing });
createTestStore({ checkout: paymentFailed });
createTestStore({ checkout: orderConfirmed });
&lt;/code&gt;&lt;/pre&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 &quot;payment failed → retry&quot; 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;hr&gt;
&lt;h3 id=&quot;your-qa-team-had-a-superpower-and-you-took-it-away&quot;&gt;Your QA Team Had A Superpower And You Took It Away&lt;/h3&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 &quot;I can't reproduce it.&quot; No guessing at sequences. No &quot;works on my machine.&quot;&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 &quot;what state was the application in when this happened.&quot; 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;hr&gt;
&lt;h3 id=&quot;the-caching-myth&quot;&gt;The Caching Myth&lt;/h3&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;hr&gt;
&lt;h3 id=&quot;two-mental-models-is-not-separation-of-concerns&quot;&gt;Two Mental Models Is Not Separation Of Concerns&lt;/h3&gt;
&lt;p&gt;The recommended pattern for &quot;doing Redux and React Query correctly&quot; is: React Query owns server state, Redux owns client state. Sounds clean. In practice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// &quot;Clean separation&quot;
const selectedId = useSelector(s =&amp;gt; s.ui.selectedGameId);  // Redux
const { data: game } = useQuery({                           // React Query
  queryKey: ['game', selectedId],
  queryFn: () =&amp;gt; fetchGame(selectedId),
  enabled: !!selectedId,                                    // coordination between systems
});
&lt;/code&gt;&lt;/pre&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;hr&gt;
&lt;h3 id=&quot;boilerplate-that-describes-business-logic-is-a-specification&quot;&gt;Boilerplate That Describes Business Logic Is A Specification&lt;/h3&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;hr&gt;
&lt;h3 id=&quot;react-s-own-premise&quot;&gt;React's Own Premise&lt;/h3&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;hr&gt;
&lt;h3 id=&quot;the-verdict&quot;&gt;The Verdict&lt;/h3&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 &quot;this only matters for large, complex apps&quot; — 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 &quot;just a small app.&quot;&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;hr&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 rel=&quot;nofollow&quot; href=&quot;https://github.com/ArtemX9/grimoire&quot;&gt;Grimoire&lt;/a&gt; — a self-hosted game backlog manager with Steam, PSN &amp;amp; Xbox sync and AI recommendations. &lt;a rel=&quot;nofollow&quot; href=&quot;https://trukhanoff.dev&quot;&gt;trukhanoff.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21088/the-code-you-ship-today-will-be-read-by-someone-who-wasnt-in-the-room</guid>
<pubDate>Sun, 21 Jun 2026 17:36:19 +0000</pubDate>
</item>
<item>
<title>Mastering Redux Toolkit By Building A Shopping Cart</title>
<link>https://coderlegion.com/7549/mastering-redux-toolkit-by-building-a-shopping-cart</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Redux Toolkit  (RTK) is the official, recommended way to write Redux logic.&lt;br&gt;
 It was designed to fix the long-standing complaints developers had about Redux:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redux was too “boilerplate-heavy”&lt;/li&gt;
&lt;li&gt;Setting up a store was complicated&lt;/li&gt;
&lt;li&gt;Writing reducers with switch statements felt repetitive&lt;/li&gt;
&lt;li&gt;Immutable updates were hard and easy to get wrong&lt;/li&gt;
&lt;li&gt;Common patterns (thunks, immutable updates, devtools) required many&lt;br&gt;
separate libraries and configs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redux Toolkit solves these by bundling the best Redux practices into one package with a simple API.&lt;br&gt;
 RTK is built around three ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplicity: Write less code.&lt;/li&gt;
&lt;li&gt;Predictability: Opinionated patterns to avoid mistakes.&lt;/li&gt;
&lt;li&gt;Power: Includes tools for async logic, immutable updates, and store&lt;br&gt;
setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-redux-toolkit-exists&quot;&gt;Why Redux Toolkit Exists&lt;/h3&gt;
&lt;p&gt;Before RTK, a typical Redux app required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;action types&lt;/li&gt;
&lt;li&gt;action creators&lt;/li&gt;
&lt;li&gt;reducers with switch statements&lt;/li&gt;
&lt;li&gt;manual immutable updates&lt;/li&gt;
&lt;li&gt;configuring applyMiddleware&lt;/li&gt;
&lt;li&gt;installing thunk, devtools enhancer, combineReducers, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This made Redux feel complicated for beginners.&lt;br&gt;
Redux Toolkit reduces all that to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;createSlice&lt;/li&gt;
&lt;li&gt;configureStore&lt;/li&gt;
&lt;li&gt;createAsyncThunk&lt;/li&gt;
&lt;li&gt;createReducer and createAction (lower-level APIs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With RTK:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reducers are auto-generated from slices&lt;/li&gt;
&lt;li&gt;immutable updates are done automatically using Immer&lt;/li&gt;
&lt;li&gt;store setup is one line&lt;/li&gt;
&lt;li&gt;async logic is simpler&lt;/li&gt;
&lt;li&gt;no need for switch statements or constants&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article, we will learn a lot about Redux Toolkit by building a shopping cart. And to gain the most from this article, I have listed the prerequisites below in the next section.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;NodeJS should be installed on your system&lt;/li&gt;
&lt;li&gt;Knowledge of React&lt;/li&gt;
&lt;li&gt;Knowledge of JavaScript&lt;/li&gt;
&lt;li&gt;Knowledge of Redux is not required, but it is an added advantage&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;To get started, clone the starter project by running the code below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/lawrence-eagles/shopping-cart-starter shopping-cart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then change directory by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd shopping-cart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now start the app by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we get:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16999566077075467075&quot; alt=&quot;starter image&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;building-a-shopping-cart&quot;&gt;Building A Shopping Cart&lt;/h2&gt;
&lt;p&gt;In this section, we will focus on building a shopping cart. And to do this, we first need to set up our &lt;code&gt;store&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;configure-the-store&quot;&gt;Configure the Store&lt;/h3&gt;
&lt;p&gt;Redux store and Redux are kind of used interchangeably; both stand for a container for JavaScript apps. And this stores the whole state of the app in an immutable object tree.&lt;/p&gt;
&lt;p&gt;To configure the Redux store, create an app directory inside the src directory. Then create a file named store.js inside the src directory. Now add the code below to the store.js file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// app/store.js

import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import counterReducer from &quot;../features/counter/counterSlice&quot;;

export const store = configureStore({
  reducer: { },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After configuring the store, we need to provide the global state to our application.  &lt;/p&gt;
&lt;h3 id=&quot;provide-the-store-to-react&quot;&gt;Provide the Store to React&lt;/h3&gt;
&lt;p&gt;To provide the store to React, replace the code in the main.jsx with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from 'react';
import { createRoot } from 'react-dom/client';
import { store } from './store';
import { Provider } from 'react-redux';

import './index.css';
import App from './App';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
 &amp;lt;React.StrictMode&amp;gt;
   &amp;lt;Provider store={store}&amp;gt;
     &amp;lt;App /&amp;gt;
   &amp;lt;/Provider&amp;gt;
 &amp;lt;/React.StrictMode&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we used the Provider component to provide our store — our global state, to our application. Next, we create a slice.&lt;/p&gt;
&lt;h3 id=&quot;create-a-slice&quot;&gt;Create a Slice&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;slice&lt;/code&gt; in Redux Toolkit is a collection of Redux logic for a single feature of your application. It bundles state, reducers, and action creators into one cohesive unit.&lt;br&gt;
Think of a &lt;code&gt;slice&lt;/code&gt; as one feature = one slice.&lt;br&gt;
In this section, we will create the cart &lt;code&gt;slice&lt;/code&gt; and the modal &lt;code&gt;slice&lt;/code&gt; for the cart feature and the modal feature. To do this, create a folder called &lt;code&gt;features&lt;/code&gt; in the &lt;code&gt;src&lt;/code&gt; directory and create a folder named &lt;code&gt;cart&lt;/code&gt; inside the &lt;code&gt;features&lt;/code&gt; directory. Next, create a file named &lt;code&gt;cartSlice.js&lt;/code&gt; in the &lt;code&gt;cart&lt;/code&gt; folder and add the following code to the &lt;code&gt;cartSlice.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { createSlice } from &quot;@reduxjs/toolkit&quot;;
import cartItems from &quot;../../cartItems&quot;;

const initialState = {
 cartItems,
 amount: 0,
 total: 0,
 isLoading: false,
};

const cartSlice = createSlice({
 name: &quot;cart&quot;,
 initialState,
 reducers: {
   clearCart: (state) =&amp;gt; {
     state.cartItems = [];
   },
   removeItem: (state, action) =&amp;gt; {
     const itemId = action.payload;
     state.cartItems = state.cartItems?.filter((item) =&amp;gt; item.id !== itemId);
   },
   increase: (state, { payload }) =&amp;gt; {
     const cartItem = state.cartItems.find((item) =&amp;gt; item.id === payload);
     cartItem.amount += 1;
   },
   decrease: (state, { payload }) =&amp;gt; {
     const cartItem = state.cartItems.find((item) =&amp;gt; item.id === payload);
     cartItem.amount -= 1;
   },
   calculateTotals: (state) =&amp;gt; {
     let amount = 0;
     let total = 0;

     state.cartItems.forEach((item) =&amp;gt; {
       amount += item.amount;
       total += item.amount * item.price;
     });

     state.amount = amount;
     state.total = total;
   },
 },
});

// console.log(&quot;cartSlice&quot;, cartSlice)

export const { clearCart, removeItem, increase, decrease, calculateTotals } =
 cartSlice.actions;

export default cartSlice.reducer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, the &lt;code&gt;initialState&lt;/code&gt; has four properties: the cartItems, which is the cartItems data, amount, which is 0, total, which is 0, and isLoading, which is false.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;createSlice&lt;/code&gt;, we created five reducers to handle different actions. These reducers are &lt;code&gt;clearCart&lt;/code&gt;, &lt;code&gt;removeItem&lt;/code&gt;, &lt;code&gt;increase&lt;/code&gt;, &lt;code&gt;decrease&lt;/code&gt;, and &lt;code&gt;calculateTotals&lt;/code&gt;. Then we exported five action creators, which are automatically created with the same name as the reducers provided. Redux Toolkit automatically creates these action creators for us. &lt;/p&gt;
&lt;p&gt;Also, RTK uses Immer.js internally, allowing you to write “mutating” logic safely as seen in the reducers in the code above.&lt;br&gt;
Finally, Redux Toolkit automatically generates a slice reducer, which is exported using the default export.&lt;br&gt;
Next, in the &lt;code&gt;features&lt;/code&gt; directory, create a folder called &lt;code&gt;modal&lt;/code&gt; and inside the &lt;code&gt;modal&lt;/code&gt; folder create a file called &lt;code&gt;modalSlice.js&lt;/code&gt; and add the code below to the &lt;code&gt;modalSlice.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialState  = {
   isOpen: false,
}

const modalSlice = createSlice({
   name: 'modal',
   initialState,
   reducers: {
       openModal: (state, action) =&amp;gt; {
           state.isOpen = true;
       },
       closeModal: (state, action) =&amp;gt; {
           state.isOpen = false;
       }
   }
})

export const {openModal, closeModal} = modalSlice.actions;
export default modalSlice.reducer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, the &lt;code&gt;initialState&lt;/code&gt; has only the &lt;code&gt;isOpen&lt;/code&gt; property, which is &lt;code&gt;false&lt;/code&gt;. And in the &lt;code&gt;createSlice&lt;/code&gt; function, we created two reducers: &lt;code&gt;openModal&lt;/code&gt; and &lt;code&gt;closeModal&lt;/code&gt;. Additionally, two action creators with the same names as these reducers are automatically created for us by Redux Toolkit, and they are exported. &lt;br&gt;
Finally, a slice reducer is automatically created by Redux Toolkit, and this is exported using the default export.&lt;br&gt;
Next, we need to add both slice reducers to the application store, and we will do this in the next section.&lt;/p&gt;
&lt;h3 id=&quot;update-store&quot;&gt;Update Store&lt;/h3&gt;
&lt;p&gt;To add the slice reducers to the application store, update the code in the &lt;code&gt;store.js&lt;/code&gt; file with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import cartReducer from &quot;./features/cart/cartSlice&quot;;
import modalReducer from &quot;./features/modal/modalSlice&quot;;

export const store = configureStore({
   reducer: {
       cart: cartReducer,
       modal: modalReducer,
   },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now both the &lt;code&gt;cartReducer&lt;/code&gt; and the &lt;code&gt;modalReducer&lt;/code&gt; are available to the entire app through the &lt;code&gt;provider&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;use-the-store-in-components&quot;&gt;Use the Store in Components&lt;/h3&gt;
&lt;p&gt;To do this, add the following code to the &lt;code&gt;App.jsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { useEffect } from &quot;react&quot;;
import Navbar from &quot;./components/Navbar&quot;;
import Modal from &quot;./components/Modal&quot;;
import CartContainer from &quot;./components/CartContainer&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;
import { calculateTotals } from &quot;./features/cart/cartSlice&quot;;

function App() {
 const { cartItems, isLoading } = useSelector((store) =&amp;gt; store.cart);
 const { isOpen } = useSelector((store) =&amp;gt; store.modal);
 const dispatch = useDispatch();

 useEffect(() =&amp;gt; {
   dispatch(calculateTotals());
 }, [cartItems]);

 return (
   &amp;lt;&amp;gt;
     {isLoading &amp;amp;&amp;amp; (
       &amp;lt;div className=&quot;loading&quot;&amp;gt;
         &amp;lt;h1&amp;gt;Loading...&amp;lt;/h1&amp;gt;
       &amp;lt;/div&amp;gt;
     )}
     &amp;lt;main&amp;gt;
       {isOpen &amp;amp;&amp;amp; !isLoading &amp;amp;&amp;amp; &amp;lt;Modal /&amp;gt;}

       {!isLoading &amp;amp;&amp;amp; (
         &amp;lt;&amp;gt;
           &amp;lt;Navbar /&amp;gt;
           &amp;lt;CartContainer /&amp;gt;
         &amp;lt;/&amp;gt;
       )}
     &amp;lt;/main&amp;gt;
   &amp;lt;/&amp;gt;
 );
}
export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, &lt;code&gt;cartItems&lt;/code&gt;, &lt;code&gt;isLoading&lt;/code&gt;, and &lt;code&gt;isOpen&lt;/code&gt; are destructured from the &lt;code&gt;store&lt;/code&gt; using the &lt;code&gt;useSelector&lt;/code&gt; hook provided by react-redux.&lt;br&gt;
Also, react-redux provides the &lt;code&gt;useDispatch&lt;/code&gt; hook, which returns the store’s &lt;code&gt;dispatch&lt;/code&gt; function. Additionally,  calling &lt;code&gt;dispatch(calculateTotals())&lt;/code&gt; sends the action to the Redux store, and the slice reducer handles it and updates the state.&lt;/p&gt;
&lt;p&gt;Next, we will create the &lt;code&gt;Modal&lt;/code&gt;, the &lt;code&gt;Navbar&lt;/code&gt;, and the &lt;code&gt;CartContainer&lt;/code&gt; components. &lt;br&gt;
To do this, in the &lt;code&gt;src&lt;/code&gt; directory, create a folder named &lt;code&gt;components&lt;/code&gt; and in the components folder, create a file named &lt;code&gt;Modal.jsx&lt;/code&gt; and add the following code to the &lt;code&gt;Modal.jsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from 'react'
import { useDispatch } from 'react-redux'
import { clearCart } from '../features/cart/cartSlice'
import { closeModal } from '../features/modal/modalSlice'

const Modal = () =&amp;gt; {
 const dispatch = useDispatch()
 return (
   &amp;lt;aside className=&quot;modal-container&quot;&amp;gt;
       &amp;lt;div className=&quot;modal&quot;&amp;gt;
           &amp;lt;h4&amp;gt;remove all items from your shopping cart?&amp;lt;/h4&amp;gt;
           &amp;lt;div className=&quot;btn-container&quot;&amp;gt;
               &amp;lt;button type=&quot;button&quot; className='btn confirm-btn'
               onClick={()=&amp;gt; {
                 dispatch(clearCart())
                 dispatch(closeModal())
               }}&amp;gt;confirm&amp;lt;/button&amp;gt;

               &amp;lt;button type=&quot;button&quot; className='btn clear-btn'
               onClick={()=&amp;gt; dispatch(closeModal()) }&amp;gt;cancel&amp;lt;/button&amp;gt;
           &amp;lt;/div&amp;gt;
       &amp;lt;/div&amp;gt;
   &amp;lt;/aside&amp;gt;
 )
}

export default Modal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a file named &lt;code&gt;Navbar.jsx&lt;/code&gt; inside the &lt;code&gt;components&lt;/code&gt; folder that is in the &lt;code&gt;src&lt;/code&gt; directory. And add the following code to the &lt;code&gt;Navbar.jsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import {CartIcon} from &quot;../icons&quot;;
import { useSelector } from &quot;react-redux&quot;;

const Navbar = () =&amp;gt; {
   const {amount} = useSelector((store)=&amp;gt;store.cart)
   
   return(
       &amp;lt;nav className='nav-center'&amp;gt;
           &amp;lt;h3&amp;gt;redux toolkit&amp;lt;/h3&amp;gt;
           &amp;lt;div className=&quot;nav-container&quot;&amp;gt;
               &amp;lt;CartIcon /&amp;gt;
               &amp;lt;div className=&quot;amount-container&quot;&amp;gt;
                   &amp;lt;p className=&quot;total-amount&quot;&amp;gt;{amount}&amp;lt;/p&amp;gt;
               &amp;lt;/div&amp;gt;
           &amp;lt;/div&amp;gt;
       &amp;lt;/nav&amp;gt;
   )
}

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, create a file named &lt;code&gt;CartContainer&lt;/code&gt; in the &lt;code&gt;components&lt;/code&gt; directory and add the following code to the &lt;code&gt;CartContainer&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from 'react'
import CartItem from './CartItem'
import { useSelector, useDispatch } from 'react-redux'
import { openModal } from '../features/modal/modalSlice'

const CartContainer = () =&amp;gt; {
 const dispatch = useDispatch();
 const {cartItems, total, amount} = useSelector((store)=&amp;gt;store.cart)

 return (
   &amp;lt;&amp;gt;
     {amount &amp;lt; 1 &amp;amp;&amp;amp; (
       &amp;lt;section className=&quot;cart&quot;&amp;gt;
         &amp;lt;header&amp;gt;
           &amp;lt;h2&amp;gt;
             your bag
           &amp;lt;/h2&amp;gt;
           &amp;lt;h4 className=&quot;empty-cart&quot;&amp;gt;is currently empty&amp;lt;/h4&amp;gt;
         &amp;lt;/header&amp;gt;
       &amp;lt;/section&amp;gt;
     ) }

     {amount &amp;amp;&amp;amp; (
       &amp;lt;section className=&quot;cart&quot;&amp;gt;
         &amp;lt;header&amp;gt;
           &amp;lt;h2&amp;gt;your bag&amp;lt;/h2&amp;gt;
         &amp;lt;/header&amp;gt;
         &amp;lt;div&amp;gt;
           {cartItems.map((item)=&amp;gt; (
             &amp;lt;CartItem key={item.id} {...item} /&amp;gt;
           ))}
         &amp;lt;/div&amp;gt;
         &amp;lt;footer&amp;gt;
           &amp;lt;hr /&amp;gt;
           &amp;lt;div className=&quot;cart-total&quot;&amp;gt;
             &amp;lt;h4&amp;gt;
               total &amp;lt;span&amp;gt;${total.toFixed(2)}&amp;lt;/span&amp;gt;
             &amp;lt;/h4&amp;gt;
           &amp;lt;/div&amp;gt;
           &amp;lt;button className=&quot;btn clear-btn&quot;
             onClick={()=&amp;gt;dispatch(openModal())}&amp;gt;clear cart&amp;lt;/button&amp;gt;
         &amp;lt;/footer&amp;gt;
       &amp;lt;/section&amp;gt;
     )}
   &amp;lt;/&amp;gt;
 )
}

export default CartContainer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we see that we &lt;code&gt;map&lt;/code&gt; through the &lt;code&gt;cartItems&lt;/code&gt; and pass each &lt;code&gt;item&lt;/code&gt; to the &lt;code&gt;CartItem&lt;/code&gt; component. So we need to create the &lt;code&gt;CartItem&lt;/code&gt; component. And to do this, create a file called &lt;code&gt;CartItem.jsx&lt;/code&gt; in the &lt;code&gt;components&lt;/code&gt; directory and add the following code to the &lt;code&gt;CartItem.jsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from 'react'
import { ChevronDown, ChevronUp  } from '../icons'
import { useDispatch } from 'react-redux'
import { removeItem, increase, decrease } from '../features/cart/cartSlice'

const CartItem = ({id, img, title, price, amount}) =&amp;gt; {
 const dispatch = useDispatch()
 return (
   &amp;lt;article className=&quot;cart-item&quot;&amp;gt;
     &amp;lt;img src={img} alt={title} /&amp;gt;
     &amp;lt;div&amp;gt;
       &amp;lt;h4&amp;gt;{title}&amp;lt;/h4&amp;gt;
       &amp;lt;h4 className=&quot;item-price&quot;&amp;gt;${price}&amp;lt;/h4&amp;gt;
       &amp;lt;button className=&quot;remove-btn&quot;
         onClick={()=&amp;gt; dispatch(removeItem(id))}&amp;gt;remove&amp;lt;/button&amp;gt;
     &amp;lt;/div&amp;gt;
     &amp;lt;div&amp;gt;
       &amp;lt;button className=&quot;amount-btn&quot;
         onClick={()=&amp;gt; dispatch(increase(id))}&amp;gt;
         &amp;lt;ChevronUp /&amp;gt;
       &amp;lt;/button&amp;gt;
       &amp;lt;p className=&quot;amount&quot;&amp;gt;{amount}&amp;lt;/p&amp;gt;
       &amp;lt;button className=&quot;amount-btn&quot;
       onClick={()=&amp;gt; {
         if(amount === 1) {
           dispatch(removeItem(id))
           return;
         }
         dispatch(decrease(id))}}&amp;gt;
         &amp;lt;ChevronDown /&amp;gt;
       &amp;lt;/button&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/article&amp;gt;
 )
}

export default CartItem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you view the application, you get:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14179483867827415844&quot; alt=&quot;shop-cart&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Redux Toolkit has completely transformed how developers use Redux.&lt;br&gt;
What was once seen as too complicated is now simple, elegant, and enjoyable.&lt;br&gt;
The above application is a simple application that shows how Redux Toolkit is set up and the overall structure of an application. However, I do hope that by following this application, you will get an understanding of Redux Toolkit and how it is used with React.&lt;br&gt;
If you’re still managing state with plain Redux, now is the time to switch. You’ll write cleaner code, reduce bugs, and enjoy a better development experience.&lt;/p&gt;
</description>
<category>Redux</category>
<guid isPermaLink="true">https://coderlegion.com/7549/mastering-redux-toolkit-by-building-a-shopping-cart</guid>
<pubDate>Wed, 19 Nov 2025 21:53:51 +0000</pubDate>
</item>
<item>
<title>The Complete Guide to Angular Subscriptions: Template vs. Component Logic</title>
<link>https://coderlegion.com/7552/the-complete-guide-to-angular-subscriptions-template-vs-component-logic</link>
<description>&lt;p&gt;Every Angular developer eventually faces the dilemma: &lt;em&gt;How do I get my data out of the Observable?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are two correct answers, and they serve different purposes. Let's look at the code for both.&lt;/p&gt;
&lt;h4 id=&quot;scenario-1-pure-display-the-happy-path-&quot;&gt;Scenario 1: Pure Display (The Happy Path)&lt;/h4&gt;
&lt;p&gt;If your data is just going onto the screen, use the &lt;strong&gt;Async Pipe&lt;/strong&gt;. It is Declarative, meaning you describe &lt;em&gt;what&lt;/em&gt; you want (the stream value), not &lt;em&gt;how&lt;/em&gt; to get it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Code:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// component.ts
data$ = this.service.getData(); // No subscription here!
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;{{ data$ | async }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Use this 90% of the time. It prevents memory leaks automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;scenario-2-component-logic-the-necessary-evil-&quot;&gt;Scenario 2: Component Logic (The Necessary Evil)&lt;/h4&gt;
&lt;p&gt;Sometimes, the Async Pipe isn't enough. You might need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filter data based on local state.&lt;/li&gt;
&lt;li&gt;Trigger a side effect (like a Toast notification).&lt;/li&gt;
&lt;li&gt;Patch a Form Control value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here, you must subscribe. And to avoid Zombie Components (listeners that stay alive after the component dies), you need the &lt;code&gt;takeUntil&lt;/code&gt; pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Code:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export class MyComponent implements OnDestroy {
  private destroy$ = new Subject&amp;lt;void&amp;gt;();

  ngOnInit(): void {
    this.dataService.getData()
      .pipe(takeUntil(this.destroy$)) // Use takeUntil with the destroy$ subject
      .subscribe(response =&amp;gt; {
        this.data = response;
        this.form.patchValue(response); // Specific logic!
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next(); // Emit a value to signal completion
    this.destroy$.complete(); // Complete the subject
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt;&lt;br&gt;
Don't be afraid of manual subscriptions, but respect them. They require explicit cleanup logic. If you can avoid them by using &lt;code&gt;| async&lt;/code&gt;, do it. If not, use &lt;code&gt;takeUntil&lt;/code&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/7552/the-complete-guide-to-angular-subscriptions-template-vs-component-logic</guid>
<pubDate>Wed, 19 Nov 2025 18:25:11 +0000</pubDate>
</item>
<item>
<title>Stop Fetching Data in useEffect: Redux Edition</title>
<link>https://coderlegion.com/4332/stop-fetching-data-in-useeffect-redux-edition</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/4231/stop-fetching-data-in-useeffect-youre-not-writing-jquery-anymore&quot;&gt;Part 1&lt;/a&gt;, we talked about why fetching data directly inside useEffect is a flawed approach — tightly coupling component lifecycle to data-fetching logic, leading to unmanageable side effects, broken UX, and stale state bugs. We compared it to jQuery’s imperative data flows and showed how React in 2025 demands a more declarative mental model.&lt;/p&gt;
&lt;p&gt;But the story doesn’t end there.&lt;/p&gt;
&lt;p&gt;In this second part, we tackle an even more common anti-pattern, especially in Redux-heavy codebases:&lt;br&gt;
&lt;strong&gt;Dispatching async thunks&lt;/strong&gt; from &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;useEffect(() =&amp;gt; {
  dispatch(fetchUserData(userId))
}, [userId])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It might look cleaner than raw &lt;code&gt;fetch()&lt;/code&gt;, but it inherits the same rot underneath.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-still-exists&quot;&gt;Why This Pattern Still Exists&lt;/h3&gt;
&lt;p&gt;Before React Hooks, data fetching in Redux apps happened in &lt;code&gt;componentDidMount()&lt;/code&gt;, with connected components via &lt;code&gt;connect()&lt;/code&gt; HOCs. That &lt;a rel=&quot;nofollow&quot; href=&quot;https://react-redux.js.org/api/connect&quot;&gt;lifecycle-to-dispatch model&lt;/a&gt; was &lt;strong&gt;normative&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When hooks arrived with &lt;code&gt;useEffect&lt;/code&gt;, developers naturally ported this over. This became the de facto pattern. Even the official React-Redux docs &lt;a rel=&quot;nofollow&quot; href=&quot;https://react-redux.js.org/api/hooks#usedispatch&quot;&gt;show this&lt;/a&gt; for simple use cases.&lt;/p&gt;
&lt;p&gt;It was a one-to-one migration. And at the time, it made sense.&lt;/p&gt;
&lt;p&gt;You needed to kick off a thunk or saga somewhere, and &lt;code&gt;useEffect&lt;/code&gt; was the hammer for all side-effect nails.&lt;/p&gt;
&lt;p&gt;But Redux evolved.&lt;/p&gt;
&lt;p&gt;As &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/acemarke?lang=en&quot;&gt;Mark Erikson&lt;/a&gt; (Redux maintainer) has repeatedly &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#react-redux-and-rendering-behavior&quot;&gt;emphasized&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;You probably don’t need &lt;code&gt;useEffect&lt;/code&gt;. And if you're dispatching from it, it's likely a code smell.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yet, many codebases (even recent ones) still cling to the old &lt;code&gt;dispatch-in-useEffect&lt;/code&gt; dance. Let’s unpack why that’s a problem.&lt;/p&gt;
&lt;h3 id=&quot;the-core-problem&quot;&gt;The Core Problem&lt;/h3&gt;
&lt;p&gt;This pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delays data availability until after first render&lt;/li&gt;
&lt;li&gt;Forces you to manage loading and error state manually&lt;/li&gt;
&lt;li&gt;Ties async logic to the lifecycle, not the state graph&lt;/li&gt;
&lt;li&gt;Doesn’t support SSR or Suspense&lt;/li&gt;
&lt;li&gt;Is verbose, hard to test, and easy to misuse&lt;/li&gt;
&lt;li&gt;Encourages repetition (every page/component reimplements fetch logic)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worse, this makes your components &lt;strong&gt;non-composable&lt;/strong&gt;. You can’t reuse this logic in another component without copy-pasting or lifting.&lt;/p&gt;
&lt;p&gt;You're not modeling &lt;strong&gt;data&lt;/strong&gt; — you're modeling &lt;strong&gt;timing&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;but-isn-t-redux-still-useful-&quot;&gt;But Isn’t Redux Still Useful?&lt;/h4&gt;
&lt;p&gt;It is — but not for remote server data.&lt;/p&gt;
&lt;p&gt;Redux is &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.isquaredsoftware.com/2021/01/context-redux-differences/&quot;&gt;great&lt;/a&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Global, non-remote state (auth, UI toggles, preferences)&lt;/li&gt;
&lt;li&gt;Workflow state (multi-step forms, undo/redo, complex user flows)&lt;/li&gt;
&lt;li&gt;Cross-cutting logic (feature flags, permission checks)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fetching lists&lt;/li&gt;
&lt;li&gt;User profiles&lt;/li&gt;
&lt;li&gt;Paginated feeds&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Dashboard metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...classic Redux is &lt;em&gt;too much ceremony&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You don’t need reducers, actions, thunks, and selectors just to load some damn data.&lt;/p&gt;
&lt;p&gt;You need a &lt;strong&gt;query layer&lt;/strong&gt; — not a state machine factory.&lt;/p&gt;
&lt;h4 id=&quot;is-dispatch-in-useeffect-always-wrong-&quot;&gt;Is dispatch() in useEffect() Always Wrong?&lt;/h4&gt;
&lt;p&gt;Not strictly.&lt;/p&gt;
&lt;p&gt;Sometimes you genuinely need to dispatch on mount, maybe to initialize a websocket, fire a metrics event, or pre-warm a cache.&lt;/p&gt;
&lt;p&gt;But when you're fetching &lt;strong&gt;render-critical data&lt;/strong&gt;, dispatching inside &lt;code&gt;useEffect&lt;/code&gt; means you're reacting &lt;strong&gt;too late&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You’ve already rendered once.&lt;br&gt;
You’ve already missed the chance to prefetch or SSR.&lt;br&gt;
You’ve already broken the declarative contract.&lt;/p&gt;
&lt;h3 id=&quot;if-you-have-to-use-classic-redux&quot;&gt;If You &lt;em&gt;Have&lt;/em&gt; to Use Classic Redux&lt;/h3&gt;
&lt;p&gt;Encapsulate the dispatch logic in a custom hook to decouple data orchestration from your component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function useUserData(userId) {
  const dispatch = useDispatch()
  const data = useSelector(state =&amp;gt; state.users[userId])
  const loading = useSelector(state =&amp;gt; state.loading.users[userId])

  useEffect(() =&amp;gt; {
    dispatch(fetchUserData(userId))
  }, [userId])

  return { data, loading }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then use declaratively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { data, loading } = useUserData(userId)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your component stays declarative&lt;/li&gt;
&lt;li&gt;The effect logic is testable and reusable&lt;/li&gt;
&lt;li&gt;You reduce boilerplate inside the view layer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still far from ideal — but survivable.&lt;/p&gt;
&lt;h3 id=&quot;so-what-should-you-do-instead-&quot;&gt;So What Should You Do Instead?&lt;/h3&gt;
&lt;p&gt;Data fetching deserves better than a &lt;code&gt;useEffect&lt;/code&gt; hack job. If your mental model is still stuck in 2019, it's time for a reset.&lt;/p&gt;
&lt;p&gt;In Part 3, we’ll explore a &lt;strong&gt;declarative routing-first approach &lt;/strong&gt; to data fetching using loader, useLoaderData, and compare that with popular solutions like RTK Query, TanStack Query, swr and even emerging Suspense APIs. We'll also address how to gracefully migrate away from this legacy pattern in large codebases.&lt;/p&gt;
&lt;p&gt;And yes — there’s a way to do all this without ditching Redux if you don’t want to.&lt;/p&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;useEffect is not a data-fetching tool&lt;/li&gt;
&lt;li&gt;Dispatching async thunks in useEffect is an outdated, fragile pattern&lt;/li&gt;
&lt;li&gt;This made sense in pre-RTK Redux, but should be retired&lt;/li&gt;
&lt;li&gt;Redux already solved this. It’s called RTK Query.&lt;/li&gt;
&lt;li&gt;RTK Query, TanStack Query, and Loader APIs offer better abstractions&lt;/li&gt;
&lt;li&gt;Declarative data &amp;gt; lifecycle-driven orchestration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re still writing async thunks just to get a list of users, it’s time to ask yourself why.&lt;/p&gt;
&lt;p&gt;You’re still not writing jQuery.&lt;br&gt;
And now — you shouldn't be writing it with Redux either.&lt;/p&gt;
</description>
<category>Frontend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4332/stop-fetching-data-in-useeffect-redux-edition</guid>
<pubDate>Wed, 30 Jul 2025 07:23:38 +0000</pubDate>
</item>
<item>
<title>Nucleux</title>
<link>https://coderlegion.com/3440/nucleux</link>
<description>&lt;h3 id=&quot;the-story-behind-nucleux&quot;&gt;The Story Behind Nucleux&lt;/h3&gt;
&lt;p&gt;Nucleux emerged from a simple frustration: existing React state management solutions either require complex provider setups (like Context API) or cause unnecessary re-renders (like most global state libraries). After trying Redux, Zustand, Jotai, and building custom solutions, I realized developers needed something that combined atomic precision with organizational clarity.&lt;/p&gt;
&lt;h3 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h3&gt;
&lt;p&gt;Nucleux uses atomic state management where each piece of state is independent. When you update an atom, only components subscribed to that specific atom re-render - no cascade effects, no provider hell.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class TodoStore extends Store {
  todos = this.atom([])
  filter = this.atom('all')
  
  addTodo(text) {
    this.todos.value = [...this.todos.value, { id: Date.now(), text }]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function TodoApp() {
  const todoStore = useStore(TodoStore)
  const todos = useValue(todoStore.todos)
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; todoStore.addTodo('New task')}&amp;gt;
        Add Todo
      &amp;lt;/button&amp;gt;
      &amp;lt;ul&amp;gt;
        {todos.map(todo =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;{todo.text}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-features&quot;&gt;Key Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero Providers&lt;/strong&gt;: Use state anywhere without wrapping components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atomic Updates&lt;/strong&gt;: Only subscribed components re-render&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organized Architecture&lt;/strong&gt;: Class-based stores group related state and methods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in Persistence&lt;/strong&gt;: Save to localStorage/AsyncStorage automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Injection&lt;/strong&gt;: Stores can easily depend on other stores&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightweight&lt;/strong&gt;: Only 3.5KB gzipped&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR Compatible&lt;/strong&gt;: Works seamlessly with Next.js and server-side rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;three-powerful-hooks&quot;&gt;Three Powerful Hooks&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;useStore(StoreClass)&lt;/code&gt; - Access store methods&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const todoStore = useStore(TodoStore)
todoStore.addTodo('New task')
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;useValue(atom | StoreClass, 'atomKey')&lt;/code&gt; - Subscribe to specific atoms&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const todos = useValue(todoStore.todos)
// or
const todos = useValue(TodoStore, 'todos')
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;useNucleux(StoreClass)&lt;/code&gt; - Get everything at once&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { todos, filter, addTodo } = useNucleux(TodoStore)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;get-involved&quot;&gt;Get Involved&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Try it&lt;/strong&gt;: &lt;code&gt;npm install nucleux&lt;/code&gt; or check the &lt;a rel=&quot;nofollow&quot; href=&quot;https://codesandbox.io/p/sandbox/nucleux-react-qw58s4&quot;&gt;live demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute&lt;/strong&gt;: Visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/martyroque/nucleux&quot;&gt;GitHub&lt;/a&gt; for issues and feature requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Full guides at &lt;a rel=&quot;nofollow&quot; href=&quot;https://nucleux.dev&quot;&gt;nucleux.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're looking for contributors who understand React performance, state management patterns, and developer experience. Whether it's bug fixes, new features, or documentation improvements - all contributions are welcome!&lt;/p&gt;
</description>
<category>Open Source</category>
<guid isPermaLink="true">https://coderlegion.com/3440/nucleux</guid>
<pubDate>Tue, 24 Jun 2025 01:46:34 +0000</pubDate>
</item>
<item>
<title>State Management Made Easy with Fluxor in Blazor</title>
<link>https://coderlegion.com/2807/state-management-made-easy-with-fluxor-in-blazor</link>
<description>&lt;p&gt;&lt;strong&gt;Exploring Actions, Effects, and the Benefits of Fluxor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modern web applications are becoming increasingly complex, and with complexity comes the need for structured state management. In the world of Blazor a popular .NET-based web UI framework .&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fluxor&lt;/strong&gt; offers a powerful yet easy-to-understand state container that brings predictable state flow to your applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fluxor&lt;/strong&gt; is a zero-boilerplate Flux/Redux library for Microsoft .NET.&lt;/p&gt;
&lt;p&gt;It brings the same clean architecture and unidirectional data flow popularized by Redux in the JavaScript ecosystem—adapted for C# developers building with Blazor.&lt;/p&gt;
&lt;p&gt;We’ll break down the core concepts of Fluxor and show how you can use Actions, Reducers, and Effects to create a clean, maintainable architecture.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt; Explore the GitHub project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;️ What is Fluxor?&lt;/p&gt;
&lt;p&gt;Fluxor provides a structured and predictable way to manage state in your Blazor application. Inspired by Redux, it embraces:&lt;/p&gt;
&lt;p&gt; Unidirectional data flow — State changes follow a clear path: Action ➝ Reducer ➝ State ➝ UI.&lt;/p&gt;
&lt;p&gt; Single source of truth — All application state lives in a centralized, immutable store.&lt;/p&gt;
&lt;p&gt; Middleware &amp;amp; Effects — Separate side-effects like API calls from your UI and business logic.&lt;/p&gt;
&lt;p&gt;Unlike some Redux implementations, Fluxor handles a lot of the boilerplate for you with C#-friendly conventions and strong typing.&lt;/p&gt;
&lt;p&gt; Actions – Declaring Intent&lt;/p&gt;
&lt;p&gt;In both Redux and Fluxor, Actions are plain messages that describe what happened. They're just &lt;br&gt;
signals , they don’t contain logic.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public record FetchItemsAction;
public record FetchItemsSuccessAction(List&amp;lt;Item&amp;gt; Items);
public record FetchItemsFailedAction(string ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚙️ Effects – Handling Side Effects&lt;/p&gt;
&lt;p&gt;Since Reducers must be pure, Effects in Fluxor allow you to perform async operations like API calls without cluttering your UI.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ItemEffects
{
    [EffectMethod]
    public async Task HandleFetchItems(FetchItemsAction action, IDispatcher dispatcher)
    {
        try
        {
            var items = await itemService.GetItemsAsync();
            dispatcher.Dispatch(new FetchItemsSuccessAction(items));
        }
        catch (Exception ex)
        {
            dispatcher.Dispatch(new FetchItemsFailedAction(ex.Message));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Effect reacts to FetchItemsAction and handles fetching data from an external service.&lt;/p&gt;
&lt;p&gt; State and Reducers – Updating the Store&lt;/p&gt;
&lt;p&gt;Reducers are responsible for updating state in response to actions. They are pure functions and return a new state based on the action received.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static class ItemReducers
{
    [ReducerMethod]
    public static ItemState OnFetchSuccess(ItemState state, FetchItemsSuccessAction action) =&amp;gt;
        state with { Items = action.Items, IsLoading = false };

    [ReducerMethod]
    public static ItemState OnFetchFailed(ItemState state, FetchItemsFailedAction action) =&amp;gt;
        state with { ErrorMessage = action.ErrorMessage, IsLoading = false };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reducers never mutate state—they return a new state object.&lt;/p&gt;
&lt;p&gt; Try It Yourself&lt;/p&gt;
&lt;p&gt;Check out the working example project to see all these concepts in action:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt;https://github.com/stevsharp/BlazorAndFluxorStateMachine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It demonstrates:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Dispatching Actions from UI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling async logic with Effects&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean Reducer logic&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blazor component integration&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Why Use Fluxor?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No boilerplate overload – clean C# syntax&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear, testable state transitions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Great for scaling large Blazor apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Familiar to those who’ve used Redux in React or Angular&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeps business logic and UI concerns separate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mrpmorris/Fluxor&quot;&gt;Fluxor GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://redux.js.org/&quot;&gt;Redux Official Site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://angular-redux.js.org/&quot;&gt;Angular Redux&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt;Blazor Documentation – Microsoft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;BlazorAndFluxorStateMachine – GitHub Sample&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List item&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/2807/state-management-made-easy-with-fluxor-in-blazor</guid>
<pubDate>Fri, 25 Apr 2025 12:59:35 +0000</pubDate>
</item>
<item>
<title>The Role of State Management in Building Scalable Front-End Applications</title>
<link>https://coderlegion.com/479/the-role-of-state-management-in-building-scalable-front-end-applications</link>
<description>&lt;h3 id=&quot;importance-of-state-management-concepts-in-front-end&quot;&gt;Importance of state management concepts in Front-End&lt;/h3&gt;
&lt;p&gt;Today’s front-end applications are more complex and powerful than ever before. The days when the logic was handled solely on the back-end, with the front-end serving as a simple data display layer, are long gone. Modern applications are expected to deliver dynamic, interactive, and seamless user experiences, often requiring sophisticated data management. One essential tool for handling this complexity is &lt;strong&gt;state management&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;State management allows developers to build scalable and maintainable applications by organizing and controlling data flows. For developers working with popular frameworks, there are ready-to-use solutions like Redux for React and NgRx for Angular. However, this article will focus on the general principles and concepts of &lt;strong&gt;state management&lt;/strong&gt;, which apply to any tool or framework.&lt;/p&gt;
&lt;h4 id=&quot;what-is-state-management-delving-into-the-theory&quot;&gt;What is State Management: Delving into the theory&lt;/h4&gt;
&lt;p&gt;In simple terms, &lt;strong&gt;“state“&lt;/strong&gt; refers to the data at a particular moment in time — such as user information, UI state, or data retrieved from APIs. This data can either be &lt;strong&gt;&lt;em&gt;local&lt;/em&gt;&lt;/strong&gt; (specific to a component or group of components) or &lt;strong&gt;&lt;em&gt;global&lt;/em&gt;&lt;/strong&gt; (accessible throughout the entire application). &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Local state&lt;/em&gt;&lt;/strong&gt; is used within a particular component to manage its data independently. For instance, imagine a form where fields depend on values from other fields. To handle these dynamic relationships, the &lt;strong&gt;&lt;em&gt;local state&lt;/em&gt;&lt;/strong&gt; can be applied to simplify data management within that form. Conversely, the &lt;strong&gt;&lt;em&gt;global state&lt;/em&gt;&lt;/strong&gt; enables data sharing across the entire application. Take user settings, for example — preferences like theme settings or language choices that should be accessible from any page. &lt;strong&gt;&lt;em&gt;Global state&lt;/em&gt;&lt;/strong&gt; provides centralized access to this data, ensuring consistency across the app.&lt;/p&gt;
&lt;p&gt;Without properly managing this data you are likely to get “spaghetti code“. Additionally, it will help you to reduce code duplication and enhance component communication.&lt;/p&gt;
&lt;h4 id=&quot;pillars-of-state-management-core-concepts&quot;&gt;Pillars of State Management: Core Concepts&lt;/h4&gt;
&lt;p&gt;Regardless of the tool or framework, some essential principles are critical to implementing effective state management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firstly&lt;/strong&gt;, there’s the &lt;strong&gt;Single Source of Truth&lt;/strong&gt; principle, which states that all data should be stored and updated in a single location. This approach simplifies data tracking and updates by ensuring there’s only one source for each piece of data, providing consistent access across the application. With centralized data handling, debugging becomes easier, especially in complex applications.&lt;/p&gt;
&lt;p&gt;Another vital principle is &lt;strong&gt;Unidirectional Data Flow&lt;/strong&gt;. This concept mandates that data flows in a predictable manner from a single source and is modified in a controlled way. This greatly reduces the potential for bugs, and provides a predictable structure, making the application’s behavior easier to understand and troubleshoot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finally&lt;/strong&gt;, there’s the principle of &lt;strong&gt;Separation of Concerns&lt;/strong&gt;, which recommends separating business logic from UI concerns. By doing so, &lt;strong&gt;state management&lt;/strong&gt; keeps business logic — such as data handling and updates independent from the UI. &lt;strong&gt;&lt;em&gt;Components can focus solely on displaying data, while complex state logic is handled separately&lt;/em&gt;&lt;/strong&gt;. This modular approach leads to a codebase that’s easier to maintain and update.&lt;/p&gt;
&lt;p&gt;These principles provide a foundation for any state management solution. Whether you choose an existing library or decide to build a custom solution, following these core ideas will help keep your application organized and scalable.&lt;/p&gt;
&lt;h4 id=&quot;the-challenges-of-unmanaged-state-what-can-it-cost-to-you&quot;&gt;The Challenges of Unmanaged State: What can it cost to you&lt;/h4&gt;
&lt;p&gt;One of the most common problems is &lt;strong&gt;data inconsistency&lt;/strong&gt;. When the state isn’t centrally managed, data inconsistencies frequently arise. Imagine a scenario where you’re editing content on the same page where it’s displayed. After making changes and pressing “Update“ or “Save“, you expect the displayed data to refresh. However, if there’s no centralized state, you might see outdated content until you refresh the entire page, creating confusion for a user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging&lt;/strong&gt; also becomes difficult when the state is not centralized. Initially, tracing bugs and errors might seem manageable, but as the application grows, tracking the source of data and understanding where updates occur can quickly become a complex and time-consuming task.&lt;/p&gt;
&lt;p&gt;Lastly, &lt;strong&gt;code duplication&lt;/strong&gt; becomes almost unavoidable without centralized state management. When components independently manage related data, duplications are likely to arise. This can quickly bloat the codebase, making it harder to maintain and increasing the chances of introducing bugs.&lt;/p&gt;
&lt;h4 id=&quot;when-to-use-state-management-tips-for-implementation&quot;&gt;When to Use State Management: Tips for Implementation&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Not every application requires state management&lt;/strong&gt;, so it’s crucial to evaluate your project’s needs. For simpler applications, such as a single landing page, state management might add unnecessary complexity. However, as soon as your application has shared data across multiple components or requires complex user interactions, state management becomes beneficial.&lt;/p&gt;
&lt;p&gt;If your application has complex user flows — where data needs to be shared across different components or pages — then state management can help you improve maintainability. For simpler projects, though, it can be overkill.&lt;/p&gt;
&lt;p&gt;If you determine that state management is necessary, &lt;strong&gt;start with a simple approach&lt;/strong&gt;. Begin by managing the state locally within individual components and only scale up to a more comprehensive state management solution as the complexity of your application grows. &lt;strong&gt;Avoid overengineering early on&lt;/strong&gt;; it’s best to let your solution evolve naturally alongside your app’s needs.&lt;/p&gt;
&lt;p&gt;As you expand your state management, &lt;strong&gt;keep it as organized as possible&lt;/strong&gt;. Avoid creating one large state object, as this can quickly become difficult to maintain. Instead, break your state into smaller, manageable sections that can be updated independently, helping keep your state clean and manageable.&lt;/p&gt;
&lt;p&gt;Another valuable tip is to &lt;strong&gt;separate your business logic from the UI&lt;/strong&gt;. Place business logic within your state management layer rather than in UI components. This makes your components &lt;strong&gt;easier to reuse&lt;/strong&gt;, as they focus solely on displaying data rather than managing it. This separation also results in &lt;strong&gt;modular&lt;/strong&gt;, &lt;strong&gt;testable code&lt;/strong&gt; that’s easier to debug and extend.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-know-your-tools-and-needs&quot;&gt;Conclusion: Know your tools and needs&lt;/h4&gt;
&lt;p&gt;If your project is expanding, now is the time to evaluate whether your current approach to &lt;strong&gt;state management&lt;/strong&gt; meets your needs. Start &lt;strong&gt;small and simple&lt;/strong&gt;, but don’t hesitate to adopt a more structured solution as your app grows. Explore tools and practices that align with your project’s requirements, and experiment with different techniques to find what works best. The right &lt;strong&gt;state management&lt;/strong&gt; approach will make your application more robust, scalable, and easier to maintain in the long run. Start integrating these principles today and see the positive impact on your development process and overall code quality!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Useful Links:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://redux.js.org/&quot;&gt;https://redux.js.org/&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://ngrx.io/guide/store/why&quot;&gt;https://ngrx.io/guide/store/why&lt;/a&gt;&lt;/p&gt;
</description>
<category>JavaScript</category>
<guid isPermaLink="true">https://coderlegion.com/479/the-role-of-state-management-in-building-scalable-front-end-applications</guid>
<pubDate>Mon, 25 Nov 2024 01:11:12 +0000</pubDate>
</item>
</channel>
</rss>