<?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: victor mwangi</title>
    <description>The latest articles on DEV Community by victor mwangi (@victor_mwangi_d224324203c).</description>
    <link>https://dev.to/victor_mwangi_d224324203c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3843544%2F4e60083a-245a-49d9-b3ec-658eb382959b.jpg</url>
      <title>DEV Community: victor mwangi</title>
      <link>https://dev.to/victor_mwangi_d224324203c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victor_mwangi_d224324203c"/>
    <language>en</language>
    <item>
      <title>How I Am Building an AI Meeting Assistant in ASP.NET Core (And Avoided Timeout Nightmares)</title>
      <dc:creator>victor mwangi</dc:creator>
      <pubDate>Wed, 03 Jun 2026 23:12:50 +0000</pubDate>
      <link>https://dev.to/victor_mwangi_d224324203c/how-i-am-building-an-ai-meeting-assistant-in-aspnet-core-and-avoided-timeout-nightmares-3mmd</link>
      <guid>https://dev.to/victor_mwangi_d224324203c/how-i-am-building-an-ai-meeting-assistant-in-aspnet-core-and-avoided-timeout-nightmares-3mmd</guid>
      <description>&lt;p&gt;For my 4rd-year Software Engineering final project at university, I’m building an AI Meeting Assistant.&lt;br&gt;
The core feature is simple: A user uploads a 20-minute audio recording of a meeting. My backend downloads it from Backblaze B2, sends it to an selfhosted transcription service to transcribe the speech, and then passes that text to Google Gemini to generate structured meeting minutes that  are  sent  to  you  email.&lt;br&gt;
There was just one massive problem: It takes over a minute to process.&lt;br&gt;
If I ran this logic inside my standard API Controller, the user’s browser would just show a loading spinner for 90 seconds, eventually hitting a 504 Gateway Timeout.&lt;br&gt;
The solution? ASP.NET Core Background Services. Here is how I implemented a background worker to handle the heavy AI lifting, and the 3 massive "Gotchas" I ran into along the way.&lt;/p&gt;

&lt;p&gt;To understand why Background Services are necessary, think of a fast-food restaurant:&lt;br&gt;
The Controller (The Cashier): The cashier takes your order, hands the ticket to the kitchen, and gives you a receipt. They don't cook the food. If the cashier goes to the back to grill your burger, the line of customers out the door gets angry.&lt;br&gt;
The BackgroundService (The Kitchen Staff): The kitchen staff doesn't interact with customers. They sit in the back, continuously checking the ticket machine. When a new ticket pops up, they cook the food.&lt;br&gt;
In ASP.NET Core, Controllers are short-lived (created and destroyed for every HTTP request). But a BackgroundService is long-lived. It is created once when the app starts, and runs continuously until the app shuts down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Building the Transcription Worker&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
In .NET, creating a background worker is incredibly easy. You just create a class that inherits from BackgroundService and override the ExecuteAsync method.&lt;br&gt;
Here is the exact skeleton of the TranscriptionWorker I built for my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TranscriptionWorker&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;_serviceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TranscriptionWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TranscriptionWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TranscriptionWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transcription Background Worker started."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// 1. Check the database for newly uploaded audio files&lt;/span&gt;
                &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pendingAudio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AudioFiles&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingAudio&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// 2. Hand it off to the processing service (Groq -&amp;gt; Gemini)&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAudioProcessingService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessAudioAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingAudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error in TranscriptionWorker loop."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// 3. Go to sleep for 30 seconds before checking again&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To turn it on, I just registered it in my Program.cs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TranscriptionWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when a user uploads an audio file, my API instantly saves it to the database with a Pending status and replies to the user: "Upload successful! We'll notify you when the minutes are ready." Meanwhile, the worker silently picks it up in the background.&lt;/p&gt;

&lt;p&gt;If you are building a .NET app that integrates with AI APIs, file processing, or heavy database tasks, stop doing it in the HTTP request pipeline. Give BackgroundService a try!&lt;br&gt;
Have you used Background Services in your projects? What kind of tasks are you running in the background? Let me know in the comments below!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>aspnetcore</category>
      <category>backend</category>
    </item>
    <item>
      <title>Discovering Google Lighthouse . A Small Tool That Changed How I See Web Development</title>
      <dc:creator>victor mwangi</dc:creator>
      <pubDate>Fri, 29 May 2026 00:32:50 +0000</pubDate>
      <link>https://dev.to/victor_mwangi_d224324203c/discovering-google-lighthouse-a-small-tool-that-changed-how-i-see-web-development-247g</link>
      <guid>https://dev.to/victor_mwangi_d224324203c/discovering-google-lighthouse-a-small-tool-that-changed-how-i-see-web-development-247g</guid>
      <description>&lt;p&gt;Today I discovered something I honestly should have explored a long time ago: Google Lighthouse.&lt;br&gt;
Funny enough, revamping my portfolio is one of those projects I kept pushing forward with the classic “I’ll do it tomorrow” mindset — and somehow tomorrow kept winning. But today I finally sat down and started improving it, and during that process, I came across Lighthouse.&lt;br&gt;
For anyone who hasn’t heard of it yet, Google Lighthouse is an open-source automated tool designed to help developers improve the quality of web pages. You can run it on almost any page — whether it’s public or behind authentication.&lt;br&gt;
What immediately caught my attention is that it audits things like:&lt;br&gt;
&lt;em&gt;Performance&lt;br&gt;
Accessibility&lt;br&gt;
SEO&lt;br&gt;
Best Practices&lt;br&gt;
And probably a few more things I’m still discovering&lt;/em&gt;&lt;br&gt;
You can run Lighthouse directly inside Chrome DevTools, through the command line, or even as a Node.js module.&lt;br&gt;
The process is simple:&lt;br&gt;
You give Lighthouse a URL, it scans the page, runs a series of audits, and then generates a detailed report showing how your website performs.&lt;br&gt;
What makes it powerful is that it doesn’t just tell you what’s wrong  it also explains:&lt;br&gt;
_&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why the issue matters&lt;/li&gt;
&lt;li&gt;How it affects users&lt;/li&gt;
&lt;li&gt;And how you can fix it
_
As a beginner software engineer and developer, I’m slowly realizing that writing code is only one part of building great applications. Performance, accessibility, maintainability, and user experience matter just as much.
And honestly, tools like Lighthouse make the learning process feel less overwhelming because they point you in the right direction.
One thing I’ll say though  don’t fall into the trap of chasing a perfect Lighthouse score instead of building useful projects. A lot of developers start optimizing numbers before validating whether the product itself solves a real problem. Lighthouse is a guide, not the final goal.
For my portfolio specifically, Lighthouse exposed a few weaknesses immediately:&lt;/li&gt;
&lt;li&gt;Large unoptimized images&lt;/li&gt;
&lt;li&gt;Accessibility issues&lt;/li&gt;
&lt;li&gt;Slow-loading assets&lt;/li&gt;
&lt;li&gt;Missing metadata
Things I probably would have ignored before.
Small discoveries like this make development more exciting for me because every new tool feels like learning how to sharpen my sword a little better.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until next time, fellow coffee addicts.&lt;/p&gt;

&lt;p&gt;Maybe next time I’ll uncover another trick to become a better developer.&lt;/p&gt;

&lt;p&gt;Adios ☕&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
      <category>react</category>
    </item>
    <item>
      <title>Stop Using useEffect for Data Fetching: Understanding TanStack Query</title>
      <dc:creator>victor mwangi</dc:creator>
      <pubDate>Wed, 27 May 2026 16:28:02 +0000</pubDate>
      <link>https://dev.to/victor_mwangi_d224324203c/stop-using-useeffect-for-data-fetching-understanding-tanstack-query-10gf</link>
      <guid>https://dev.to/victor_mwangi_d224324203c/stop-using-useeffect-for-data-fetching-understanding-tanstack-query-10gf</guid>
      <description>&lt;p&gt;If you are still fetching data inside a useEffect hook, manually managing loading states, and storing server responses inside local component state, your frontend architecture is carrying unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Managing asynchronous API data — also called Server State — is one of the hardest problems in frontend development. Trying to handle it with traditional client-side state tools like Redux, Zustand, or plain React state often leads to repetitive, fragile, and difficult-to-maintain codebases.&lt;/p&gt;

&lt;p&gt;That is where TanStack Query changes everything.&lt;/p&gt;

&lt;p&gt;It is not just a data-fetching library. It acts as:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An intelligent cache&lt;br&gt;
A server-state manager&lt;br&gt;
A synchronization layer between your frontend and backend&lt;br&gt;
A background updater for stale data&lt;/em&gt;&lt;br&gt;
Let’s break down how it actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Client State vs Server State&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before understanding TanStack Query, you need to separate two completely different categories of state.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1. Client State&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is synchronous state that exists entirely inside the browser.&lt;/p&gt;

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

&lt;p&gt;Modal visibility&lt;br&gt;
Theme toggles&lt;br&gt;
Sidebar state&lt;br&gt;
Form inputs&lt;/p&gt;

&lt;p&gt;This data is fully controlled by the frontend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2. Server State&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is asynchronous state that lives remotely on a backend server or database.&lt;/p&gt;

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

&lt;p&gt;Product lists&lt;br&gt;
User profiles&lt;br&gt;
Notifications&lt;br&gt;
Dashboard analytics&lt;br&gt;
Orders from an API&lt;/p&gt;

&lt;p&gt;Server state has unique challenges:&lt;/p&gt;

&lt;p&gt;You do not own the data&lt;br&gt;
Multiple users can modify it&lt;br&gt;
It can become outdated instantly&lt;br&gt;
It requires async network requests&lt;br&gt;
It needs synchronization&lt;/p&gt;

&lt;p&gt;Using useState + useEffect forces server state to behave like client state.&lt;/p&gt;

&lt;p&gt;That creates problems like:&lt;/p&gt;

&lt;p&gt;Duplicate requests&lt;br&gt;
Boilerplate loading logic&lt;br&gt;
Manual error handling&lt;br&gt;
Race conditions&lt;br&gt;
No caching&lt;br&gt;
Difficult synchronization&lt;/p&gt;

&lt;p&gt;TanStack Query solves these automatically.&lt;/p&gt;

&lt;p&gt;Declarative Data Fetching with useQuery&lt;/p&gt;

&lt;p&gt;TanStack Query replaces imperative fetching with a declarative approach.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The core building blocks are:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Query Key&lt;br&gt;
Query Function&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;_**&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery } from '@tanstack/react-query';

function ProductList() {
  const { data, isLoading, isError, error } = useQuery({
    queryKey: ['products'],
    queryFn: async () =&amp;gt; {
      const res = await fetch('https://api.yourdomain.com/products');

      if (!res.ok) {
        throw new Error('Failed to fetch products');
      }

      return res.json();
    },
  });

  if (isLoading) {
    return &amp;lt;div&amp;gt;Loading products...&amp;lt;/div&amp;gt;;
  }

  if (isError) {
    return &amp;lt;div&amp;gt;Error: {error.message}&amp;lt;/div&amp;gt;;
  }

  return (
    &amp;lt;ul&amp;gt;
      {data.map((product) =&amp;gt; (
        &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**&lt;br&gt;
Why This Is Powerful&lt;/p&gt;

&lt;p&gt;Instead of manually controlling the fetch lifecycle, you simply describe:&lt;/p&gt;

&lt;p&gt;“This component depends on the products query.”&lt;/p&gt;

&lt;p&gt;TanStack Query handles everything else automatically.&lt;/p&gt;

&lt;p&gt;Network Request Deduplication&lt;/p&gt;

&lt;p&gt;Imagine three different components all requesting the same product list:&lt;/p&gt;

&lt;p&gt;Sidebar&lt;br&gt;
Dashboard&lt;br&gt;
Analytics widget&lt;/p&gt;

&lt;p&gt;Without TanStack Query:&lt;/p&gt;

&lt;p&gt;3 components&lt;br&gt;
3 fetch calls&lt;br&gt;
3 HTTP requests&lt;/p&gt;

&lt;p&gt;With TanStack Query:&lt;/p&gt;

&lt;p&gt;queryKey: ['products']&lt;/p&gt;

&lt;p&gt;TanStack Query recognizes the identical query key and:&lt;/p&gt;

&lt;p&gt;Sends only ONE network request&lt;br&gt;
Shares the response across all components&lt;br&gt;
Prevents network waterfalls&lt;/p&gt;

&lt;p&gt;This significantly improves performance.&lt;/p&gt;

&lt;p&gt;Understanding the Cache Architecture&lt;/p&gt;

&lt;p&gt;TanStack Query uses a strategy called:&lt;/p&gt;

&lt;p&gt;Stale-While-Revalidate&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;/p&gt;

&lt;p&gt;Serve cached data immediately&lt;br&gt;
Fetch fresh data in the background&lt;br&gt;
Update the UI automatically if data changed&lt;/p&gt;

&lt;p&gt;This creates extremely fast-feeling applications because users instantly see cached content instead of loading spinners.&lt;/p&gt;

&lt;p&gt;Client State vs Server State&lt;/p&gt;

&lt;p&gt;Before understanding TanStack Query, you need to separate two completely different categories of state.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client State&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is synchronous state that exists entirely inside the browser.&lt;/p&gt;

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

&lt;p&gt;Modal visibility&lt;br&gt;
Theme toggles&lt;br&gt;
Sidebar state&lt;br&gt;
Form inputs&lt;/p&gt;

&lt;p&gt;This data is fully controlled by the frontend.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server State&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is asynchronous state that lives remotely on a backend server or database.&lt;/p&gt;

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

&lt;p&gt;Product lists&lt;br&gt;
User profiles&lt;br&gt;
Notifications&lt;br&gt;
Dashboard analytics&lt;br&gt;
Orders from an API&lt;/p&gt;

&lt;p&gt;Server state has unique challenges:&lt;/p&gt;

&lt;p&gt;You do not own the data&lt;br&gt;
Multiple users can modify it&lt;br&gt;
It can become outdated instantly&lt;br&gt;
It requires async network requests&lt;br&gt;
It needs synchronization&lt;/p&gt;

&lt;p&gt;Using useState + useEffect forces server state to behave like client state.&lt;/p&gt;

&lt;p&gt;That creates problems like:&lt;/p&gt;

&lt;p&gt;Duplicate requests&lt;br&gt;
Boilerplate loading logic&lt;br&gt;
Manual error handling&lt;br&gt;
Race conditions&lt;br&gt;
No caching&lt;br&gt;
Difficult synchronization&lt;/p&gt;

&lt;p&gt;TanStack Query solves these automatically.&lt;/p&gt;

&lt;p&gt;Declarative Data Fetching with useQuery&lt;/p&gt;

&lt;p&gt;TanStack Query replaces imperative fetching with a declarative approach.&lt;/p&gt;

&lt;p&gt;The core building blocks are:&lt;/p&gt;

&lt;p&gt;Query Key&lt;br&gt;
Query Function&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;_&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery } from '@tanstack/react-query';

function ProductList() {
  const { data, isLoading, isError, error } = useQuery({
    queryKey: ['products'],
    queryFn: async () =&amp;gt; {
      const res = await fetch('https://api.yourdomain.com/products');

      if (!res.ok) {
        throw new Error('Failed to fetch products');
      }

      return res.json();
    },
  });

  if (isLoading) {
    return &amp;lt;div&amp;gt;Loading products...&amp;lt;/div&amp;gt;;
  }

  if (isError) {
    return &amp;lt;div&amp;gt;Error: {error.message}&amp;lt;/div&amp;gt;;
  }

  return (
    &amp;lt;ul&amp;gt;
      {data.map((product) =&amp;gt; (
        &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;_&lt;br&gt;
Why This Is Powerful&lt;/p&gt;

&lt;p&gt;Instead of manually controlling the fetch lifecycle, you simply describe:&lt;/p&gt;

&lt;p&gt;“This component depends on the products query.”&lt;/p&gt;

&lt;p&gt;TanStack Query handles everything else automatically.&lt;/p&gt;

&lt;p&gt;Network Request Deduplication&lt;/p&gt;

&lt;p&gt;Imagine three different components all requesting the same product list:&lt;/p&gt;

&lt;p&gt;Sidebar&lt;br&gt;
Dashboard&lt;br&gt;
Analytics widget&lt;/p&gt;

&lt;p&gt;Without TanStack Query:&lt;/p&gt;

&lt;p&gt;3 components&lt;br&gt;
3 fetch calls&lt;br&gt;
3 HTTP requests&lt;/p&gt;

&lt;p&gt;With TanStack Query:&lt;/p&gt;

&lt;p&gt;queryKey: ['products']&lt;/p&gt;

&lt;p&gt;TanStack Query recognizes the identical query key and:&lt;/p&gt;

&lt;p&gt;Sends only ONE network request&lt;br&gt;
Shares the response across all components&lt;br&gt;
Prevents network waterfalls&lt;/p&gt;

&lt;p&gt;This significantly improves performance.&lt;/p&gt;

&lt;p&gt;Understanding the Cache Architecture&lt;/p&gt;

&lt;p&gt;TanStack Query uses a strategy called:&lt;/p&gt;

&lt;p&gt;Stale-While-Revalidate&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;/p&gt;

&lt;p&gt;Serve cached data immediately&lt;br&gt;
Fetch fresh data in the background&lt;br&gt;
Update the UI automatically if data changed&lt;/p&gt;

&lt;p&gt;This creates extremely fast-feeling applications because users instantly see cached content instead of loading spinners.&lt;br&gt;
Understanding staleTime&lt;/p&gt;

&lt;p&gt;Default:&lt;/p&gt;

&lt;p&gt;staleTime: 0&lt;/p&gt;

&lt;p&gt;Meaning:&lt;/p&gt;

&lt;p&gt;Data becomes stale immediately after fetching.&lt;/p&gt;

&lt;p&gt;Example optimization:&lt;/p&gt;

&lt;p&gt;staleTime: 1000 * 60 * 5&lt;/p&gt;

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

&lt;p&gt;Data is considered fresh for 5 minutes&lt;br&gt;
No refetch happens during that period&lt;br&gt;
Components reuse cached memory&lt;/p&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;p&gt;Country lists&lt;br&gt;
User settings&lt;br&gt;
Static configurations&lt;br&gt;
Understanding gcTime (Garbage Collection)&lt;/p&gt;

&lt;p&gt;Previously called cacheTime.&lt;/p&gt;

&lt;p&gt;Default:&lt;/p&gt;

&lt;p&gt;gcTime: 1000 * 60 * 5&lt;/p&gt;

&lt;p&gt;Meaning:&lt;/p&gt;

&lt;p&gt;When no component uses a query anymore:&lt;/p&gt;

&lt;p&gt;The query becomes inactive&lt;br&gt;
A garbage collection timer starts&lt;br&gt;
Cache is deleted after 5 minutes&lt;/p&gt;

&lt;p&gt;This prevents memory leaks.&lt;/p&gt;

&lt;p&gt;Automatic Background Refetching&lt;/p&gt;

&lt;p&gt;TanStack Query automatically keeps your application synchronized.&lt;/p&gt;

&lt;p&gt;It refetches data when:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1. Components Mount Again&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When a component using the query reappears.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2. Window Focus Changes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the user switches tabs and comes back, the data revalidates automatically.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;3. Network Reconnects&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the internet disconnects and reconnects, TanStack Query immediately synchronizes stale data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;4. Polling Intervals&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;refetchInterval: 10000&lt;/p&gt;

&lt;p&gt;This refetches data every 10 seconds.&lt;/p&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;p&gt;Real-time dashboards&lt;br&gt;
Notifications&lt;br&gt;
Live analytics&lt;br&gt;
Mutating Data with useMutation&lt;/p&gt;

&lt;p&gt;Fetching data is only half the story.&lt;/p&gt;

&lt;p&gt;What happens after:&lt;/p&gt;

&lt;p&gt;POST requests&lt;br&gt;
PUT updates&lt;br&gt;
DELETE operations&lt;/p&gt;

&lt;p&gt;The browser cache immediately becomes outdated.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;_&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  useMutation,
  useQueryClient,
} from '@tanstack/react-query';

const queryClient = useQueryClient();

const addProductMutation = useMutation({
  mutationFn: async (newProduct) =&amp;gt; {
    const res = await fetch(
      'https://api.yourdomain.com/products',
      {
        method: 'POST',
        body: JSON.stringify(newProduct),
      }
    );

    return res.json();
  },

  onSuccess: () =&amp;gt; {
    queryClient.invalidateQueries({
      queryKey: ['products'],
    });
  },
});
Cache Invalidation

Instead of manually updating arrays in state:

setProducts(...)

You invalidate the query:

queryClient.invalidateQueries({
  queryKey: ['products'],
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TanStack Query then:&lt;/p&gt;

&lt;p&gt;Marks the cache as stale&lt;br&gt;
Automatically refetches fresh data&lt;br&gt;
Synchronizes the UI with the database&lt;/p&gt;

&lt;p&gt;This removes fragile state management logic entirely.&lt;/p&gt;

&lt;p&gt;Using TanStack Query DevTools&lt;/p&gt;

&lt;p&gt;Debugging async cache systems with console.log becomes painful quickly.&lt;/p&gt;

&lt;p&gt;Install the DevTools:&lt;/p&gt;

&lt;p&gt;npm install @tanstack/react-query-devtools&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query';

import {
  ReactQueryDevtools,
} from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

export default function App() {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;MainApplication /&amp;gt;

      &amp;lt;ReactQueryDevtools
        initialIsOpen={false}
      /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
}_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query Lifecycle States&lt;/p&gt;

&lt;p&gt;Inside the DevTools panel, queries appear in different lifecycle states:&lt;/p&gt;

&lt;p&gt;State   Meaning&lt;br&gt;
Fresh   Data is still trusted&lt;br&gt;
Stale   Data exists but needs revalidation&lt;br&gt;
Fetching    Active network request&lt;br&gt;
Inactive    No component currently uses the query&lt;/p&gt;

&lt;p&gt;This gives you full visibility into your async cache architecture.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering Dependency Injection in .NET: A Software Engineer's Secret Weapon</title>
      <dc:creator>victor mwangi</dc:creator>
      <pubDate>Wed, 27 May 2026 15:39:10 +0000</pubDate>
      <link>https://dev.to/victor_mwangi_d224324203c/mastering-dependency-injection-in-net-a-software-engineers-secret-weapon-34b5</link>
      <guid>https://dev.to/victor_mwangi_d224324203c/mastering-dependency-injection-in-net-a-software-engineers-secret-weapon-34b5</guid>
      <description>&lt;p&gt;Imagine you are building a modern car. Instead of welding the engine directly to the chassis, you bolt it in. Why? Because if the engine breaks—or if you want to upgrade from a gas engine to an electric one—you don’t want to tear the whole car apart. You just unbolt the old one and slot the new one in.&lt;/p&gt;

&lt;p&gt;In software engineering, Dependency Injection (DI) is that bolt.&lt;/p&gt;

&lt;p&gt;If you are building applications in .NET, DI isn’t just a "nice-to-have" design pattern; it’s a core part of the framework. Let’s break down what DI is, why you need it, and how to master it in .NET.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  What on Earth is Dependency Injection?
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
Before we talk about injection, let’s talk about dependencies. If Class A uses Class B to get its job done, Class A depends on Class B. Class B is a dependency.&lt;/p&gt;

&lt;p&gt;Without DI, Class A creates Class B itself. Look at this tightly coupled mess:&lt;/p&gt;

&lt;p&gt;_C#&lt;br&gt;
public class Car&lt;br&gt;
{&lt;br&gt;
    private Engine _engine;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Car()
{
    // The car is trapped! It can only ever use this specific V8Engine.
    this._engine = new V8Engine(); 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}_&lt;br&gt;
With Dependency Injection, we invert that control (hence Inversion of Control, or IoC). Instead of the Car creating the Engine, we "inject" the engine through the constructor:&lt;/p&gt;

&lt;p&gt;_C#&lt;br&gt;
public class Car&lt;br&gt;
{&lt;br&gt;
    private readonly IEngine _engine;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The car doesn't care what kind of engine it gets, as long as it implements IEngine
public Car(IEngine engine)
{
    _engine = engine;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}_&lt;br&gt;
**&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should You Care? (The Benefits)
&lt;/h2&gt;

&lt;p&gt;**Maintainability: If you need to change how Engine works, you don't risk breaking Car.&lt;/p&gt;

&lt;p&gt;Testability: This is the big one. When writing unit tests for Car, you don't want to spin up a real database or hit a real API. With DI, you can inject a MockEngine and test the Car in isolation.&lt;/p&gt;

&lt;p&gt;Flexibility: Want to swap your SQL database for MongoDB? Just change the registration in one central place, and the rest of your app keeps running seamlessly.&lt;/p&gt;

&lt;p&gt;Service Lifetimes in .NET: The Big Three&lt;br&gt;
The built-in .NET IoC container manages the creation and disposal of your dependencies. When you register a service, you have to tell .NET how long that service should live.&lt;/p&gt;

&lt;p&gt;Choosing the wrong lifetime is the #1 cause of bugs in .NET DI. Here is the breakdown:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Transient (AddTransient)&lt;/strong&gt;&lt;br&gt;
How it works: A brand-new instance is created every single time it is requested.&lt;/p&gt;

&lt;p&gt;Best used for: Lightweight, stateless services (like a quick formatting helper or a validation service).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Scoped (AddScoped)&lt;/strong&gt;&lt;br&gt;
How it works: An instance is created once per HTTP request (in web apps). All components handling that specific web request will share that same instance.&lt;/p&gt;

&lt;p&gt;Best used for: Services that hold state for a single request, like your Entity Framework Core DbContext.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Singleton (AddSingleton)&lt;/strong&gt;&lt;br&gt;
How it works: An instance is created the first time it’s requested and lives forever until the application shuts down. Every subsequent request uses that exact same instance.&lt;/p&gt;

&lt;p&gt;Best used for: Caching services, configuration settings, or anything that is expensive to create and needs to be shared globally.&lt;/p&gt;

&lt;p&gt;⚠️ Danger Zone Warning: Never inject a Scoped service into a Singleton service. Because the Singleton lives forever, the Scoped service will get trapped inside it forever, effectively turning it into a Singleton. This is called a Captive Dependency, and it causes massive database connection bugs!&lt;/p&gt;

&lt;p&gt;Wire It Up: A Real-World .NET Example&lt;br&gt;
Let's look at how to actually implement this in a modern .NET Web API or Minimal API (Program.cs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the Contract and Implementation&lt;/strong&gt;&lt;br&gt;
_C#&lt;br&gt;
public interface IEmailService&lt;br&gt;
{&lt;br&gt;
    void SendEmail(string to, string subject);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;public class SendGridEmailService : IEmailService&lt;br&gt;
{&lt;br&gt;
    public void SendEmail(string to, string subject)&lt;br&gt;
    {&lt;br&gt;
        Console.WriteLine($"Email sent to {to} via SendGrid!");&lt;br&gt;
    }&lt;br&gt;
}_&lt;br&gt;
&lt;strong&gt;Step 2: Register it in Program.cs&lt;/strong&gt;&lt;br&gt;
Open your Program.cs file. This is where the magic happens. We register our IEmailService with the service collection.&lt;/p&gt;

&lt;p&gt;_C#&lt;br&gt;
var builder = WebApplication.CreateBuilder(args);&lt;/p&gt;

&lt;p&gt;// Register your dependency here!&lt;br&gt;
builder.Services.AddScoped();&lt;/p&gt;

&lt;p&gt;var app = builder.Build();_&lt;br&gt;
&lt;strong&gt;Step 3: Inject and Use It&lt;/strong&gt;&lt;br&gt;
Now, .NET will automatically look at the constructor of your Controllers or Minimal API endpoints, see that it requires an IEmailService, and pass it in.&lt;/p&gt;

&lt;p&gt;_C#&lt;br&gt;
app.MapPost("/register", (string email, IEmailService emailService) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    // .NET automatically provided 'emailService' for us!&lt;br&gt;
    emailService.SendEmail(email, "Welcome to our App!");&lt;br&gt;
    return Results.Ok("User registered successfully");&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;app.Run();_&lt;br&gt;
Wrapping Up&lt;br&gt;
Dependency Injection isn't just an academic concept; it's the glue that keeps professional .NET applications clean, testable, and scalable. By shifting the responsibility of object creation from your classes to the .NET framework, you write code that is ready for change.&lt;/p&gt;

&lt;p&gt;Next time you write a class, don't reach for the new keyword right away. Ask yourself: "Should this be injected instead?"&lt;/p&gt;

&lt;p&gt;What are your thoughts on .NET's built-in DI container? Have you ever run into a captive dependency nightmare? Let's chat in the comments below!&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>programming</category>
      <category>dotnet</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
