<?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>Forem: graciesharma</title>
    <description>The latest articles on Forem by graciesharma (@graciesharma).</description>
    <link>https://forem.com/graciesharma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F833705%2F139faa81-e8ef-4522-a34e-f621cb713b93.jpeg</url>
      <title>Forem: graciesharma</title>
      <link>https://forem.com/graciesharma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/graciesharma"/>
    <language>en</language>
    <item>
      <title>You're Probably Refreshing Auth Tokens Wrong. Here's a 40-Line Fix.</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:52:20 +0000</pubDate>
      <link>https://forem.com/graciesharma/youre-probably-refreshing-auth-tokens-wrong-heres-a-40-line-fix-11f6</link>
      <guid>https://forem.com/graciesharma/youre-probably-refreshing-auth-tokens-wrong-heres-a-40-line-fix-11f6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; When multiple API calls fire at once and the token is expired, most apps trigger 5-10 duplicate refresh requests simultaneously. This is called a &lt;strong&gt;token refresh stampede&lt;/strong&gt;. It causes race conditions, 401 loops, and random logouts. The fix is 40 lines of TypeScript — a shared promise reference, JWT expiry parsing without libraries, and a 60-second early expiration buffer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Bug No One Talks About
&lt;/h2&gt;

&lt;p&gt;Your app loads a dashboard. Five components mount. Five API calls fire. The access token just expired.&lt;/p&gt;

&lt;p&gt;What happens next?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /auth/refresh → 200 ✅ (new token)
POST /auth/refresh → 200 ✅ (new token... again)
POST /auth/refresh → 200 ✅ (third time)
POST /auth/refresh → 401 ❌ (refresh token already rotated)
POST /auth/refresh → 401 ❌ (same)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three of those succeed because the server hasn't rotated the refresh token yet. The last two fail because it has. Now two components get 401s. Your interceptor sees 401 and tries to refresh again. You're in a loop. The user gets logged out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a token refresh stampede.&lt;/strong&gt; And almost every auth tutorial on the internet creates this exact bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Most Solutions Are Broken
&lt;/h2&gt;

&lt;p&gt;Here's what a "typical" token refresh looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The version every tutorial teaches you&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Every concurrent caller hits this line simultaneously&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newToken&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;Five components call &lt;code&gt;getAccessToken()&lt;/code&gt; at the same time. All five see the expired token. All five call &lt;code&gt;/auth/refresh&lt;/code&gt;. Race condition achieved.&lt;/p&gt;

&lt;p&gt;Common "fixes" that still break:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Why it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boolean flag (&lt;code&gt;isRefreshing = true&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Concurrent callers skip refresh entirely and return &lt;code&gt;null&lt;/code&gt; — now they all fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;setTimeout&lt;/code&gt; retry&lt;/td&gt;
&lt;td&gt;Adds latency, still races if timing is unlucky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue with callbacks&lt;/td&gt;
&lt;td&gt;30+ lines of complexity for something that should be simple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axios interceptor with retry&lt;/td&gt;
&lt;td&gt;Re-queues the original request but can still fire multiple refreshes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Fix: Promise Deduplication in 40 Lines
&lt;/h2&gt;

&lt;p&gt;Here's the actual production code. Read it — it's short.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchAccessToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/access-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseTokenExpiry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encodedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;  &lt;span class="c1"&gt;// Expire 60 seconds early&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Return cached token if still valid&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedToken&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cachedToken&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. If a refresh is already in-flight, piggyback on it&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tokenPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetchAccessToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cachedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseTokenExpiry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&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;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tokenPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clearTokenCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cachedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tokenPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No libraries. No queues. No retry loops.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works — 3 Techniques in 40 Lines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technique 1: Promise Deduplication via Shared Reference
&lt;/h3&gt;

&lt;p&gt;This is the core insight. Instead of a boolean flag, we store &lt;strong&gt;the promise itself&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the first caller triggers a refresh, the promise is stored. Every subsequent caller &lt;strong&gt;gets the same promise&lt;/strong&gt;. They all &lt;code&gt;await&lt;/code&gt; the same network request. One fetch, N consumers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// First caller — creates the promise&lt;/span&gt;
  &lt;span class="nx"&gt;tokenPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetchAccessToken&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tokenPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Reset for next cycle&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// All callers — same promise, same result&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.finally()&lt;/code&gt; resets the reference after the fetch completes (success or failure), so the next batch of expired calls will trigger a fresh refresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this beats a boolean flag:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Boolean flag — concurrent callers get nothing&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRefreshing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Caller 2, 3, 4, 5 all get null&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Promise ref — concurrent callers get the same result&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokenPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Caller 2, 3, 4, 5 all get the token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technique 2: JWT Expiry Parsing Without Libraries
&lt;/h3&gt;

&lt;p&gt;Most apps use &lt;code&gt;jwt-decode&lt;/code&gt; (7KB) or &lt;code&gt;jsonwebtoken&lt;/code&gt; (200KB+ with deps) to read token expiry. You don't need them.&lt;/p&gt;

&lt;p&gt;A JWT is three base64 segments separated by dots. The middle one is the payload. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseTokenExpiry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Fallback: 5 min TTL&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;Three lines to parse. Zero dependencies. The &lt;code&gt;try/catch&lt;/code&gt; handles malformed tokens gracefully — if parsing fails, default to a 5-minute TTL so the app doesn't break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is safe because we're only reading the &lt;code&gt;exp&lt;/code&gt; claim client-side. We're not validating the signature — that's the server's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technique 3: 60-Second Early Expiration Buffer
&lt;/h3&gt;

&lt;p&gt;This is the one even senior devs miss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;  &lt;span class="c1"&gt;// Expire 60 seconds before actual expiry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Network latency. If the token expires at &lt;code&gt;12:00:00&lt;/code&gt; and your request reaches the server at &lt;code&gt;12:00:00.300&lt;/code&gt;, it gets rejected even though it was valid when you sent it.&lt;/p&gt;

&lt;p&gt;By treating the token as expired 60 seconds early, you guarantee the refresh happens &lt;strong&gt;before&lt;/strong&gt; any request hits the server with a stale token. No more intermittent 401s on slow connections.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring It Into Your API Client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clearTokenCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./token-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Request interceptor — inject token into every request&lt;/span&gt;
&lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addRequestInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Error interceptor — clear cache on 401 so next request triggers refresh&lt;/span&gt;
&lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addErrorInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTokenCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Before and After
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (stampede):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Component A → getToken() → fetch /refresh → new token A
Component B → getToken() → fetch /refresh → new token B
Component C → getToken() → fetch /refresh → 401 ❌ (refresh token rotated)
Component D → getToken() → fetch /refresh → 401 ❌
Component E → getToken() → fetch /refresh → 401 ❌
→ User logged out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (deduplication):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Component A → getToken() → fetch /refresh → new token
Component B → getToken() → awaits same promise ↑
Component C → getToken() → awaits same promise ↑
Component D → getToken() → awaits same promise ↑
Component E → getToken() → awaits same promise ↑
→ 1 network request, 5 components served
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Edge Cases Handled
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Token valid&lt;/td&gt;
&lt;td&gt;Returns cached token instantly, no network request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token expired, single caller&lt;/td&gt;
&lt;td&gt;Fetches new token, caches it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token expired, 5 concurrent callers&lt;/td&gt;
&lt;td&gt;One fetch, all 5 get the same token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh fails&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;null&lt;/code&gt;, resets promise so next call retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malformed JWT&lt;/td&gt;
&lt;td&gt;Falls back to 5-minute TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401 from server&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;clearTokenCache()&lt;/code&gt; forces next request to refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network timeout&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;catch&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;.finally()&lt;/code&gt; resets promise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token has no &lt;code&gt;exp&lt;/code&gt; claim&lt;/td&gt;
&lt;td&gt;Falls back to 5-minute TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Common Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Why module-level variables instead of a class or React state?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Auth tokens are app-global. Module-level &lt;code&gt;let&lt;/code&gt; gives you a singleton automatically — no React context, no provider, no class instantiation. Every import gets the same reference. Simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why not use &lt;code&gt;navigator.locks&lt;/code&gt;?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Browser support is decent but not universal. The promise reference pattern works everywhere, is simpler, and doesn't need async coordination APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Does this work with refresh token rotation?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. That's the entire point. When the server rotates the refresh token on use, the stampede causes all but the first request to fail. This fix ensures only one request is ever made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What about SSR?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The module-level variables are client-only (&lt;code&gt;"use client"&lt;/code&gt; directive). On the server, auth is handled via HTTP-only cookies — no client-side token management needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Stop building auth refresh with boolean flags and retry loops.&lt;/p&gt;

&lt;p&gt;The fix is one shared promise reference. Every concurrent caller awaits the same in-flight request. Zero stampede. Zero race conditions. Zero dependencies. 40 lines.&lt;/p&gt;

&lt;p&gt;Copy the code. Ship it. Move on to the features that actually matter.&lt;/p&gt;




</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>security</category>
      <category>react</category>
    </item>
    <item>
      <title>Google Places Autocomplete Is Completely Broken Inside Radix Modals — Here's the 3-Part Fix</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:45:08 +0000</pubDate>
      <link>https://forem.com/graciesharma/google-places-autocomplete-is-completely-broken-inside-radix-modals-heres-the-3-part-fix-1bo1</link>
      <guid>https://forem.com/graciesharma/google-places-autocomplete-is-completely-broken-inside-radix-modals-heres-the-3-part-fix-1bo1</guid>
      <description>&lt;h1&gt;
  
  
  Google Places Autocomplete Is Completely Broken Inside Radix Modals — Here's the 3-Part Fix
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Google Places Autocomplete silently breaks inside Radix UI dialogs — the dropdown renders in the DOM but is invisible, unclickable, and locked with &lt;code&gt;inert&lt;/code&gt;. One fix isn't enough. You need three: a CSS override, a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop, and an &lt;code&gt;onInteractOutside&lt;/code&gt; guard. Complete copy-paste solution at the bottom.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Bug That Will Make You Question Your Sanity
&lt;/h2&gt;

&lt;p&gt;You wire up &lt;code&gt;@react-google-maps/api&lt;/code&gt; Autocomplete inside a Radix Dialog. Standard stuff. You open the modal, start typing an address, and wait for the familiar dropdown.&lt;/p&gt;

&lt;p&gt;It never comes.&lt;/p&gt;

&lt;p&gt;You open DevTools. The &lt;code&gt;.pac-container&lt;/code&gt; is there — sitting perfectly in the DOM, fully populated with suggestions. But it's invisible. Unclickable. Slapped with an &lt;code&gt;inert&lt;/code&gt; attribute like it insulted someone.&lt;/p&gt;

&lt;p&gt;This isn't a misconfiguration. It isn't a version mismatch. It's a three-layer collision between how Radix locks down its modal environment and how Google Places injects its dropdown into the page. Every layer breaks something different. Every layer needs its own fix.&lt;/p&gt;

&lt;p&gt;I burned hours on this. Stack Overflow has fragments of answers. None of them work completely. Here's the full production fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Happens — 3 Layers Deep
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;modal={true}&lt;/code&gt; (the default), Radix Dialog does three things that independently destroy Google Places:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — &lt;code&gt;pointer-events: none&lt;/code&gt; on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Radix's &lt;code&gt;DismissableLayer&lt;/code&gt; sets &lt;code&gt;pointer-events: none&lt;/code&gt; on the entire &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; to ensure clicks outside the dialog trigger dismissal. Google's &lt;code&gt;.pac-container&lt;/code&gt; is appended as a &lt;strong&gt;direct child of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;&lt;/strong&gt; — completely outside the dialog portal. It inherits &lt;code&gt;pointer-events: none&lt;/code&gt; and becomes untouchable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — &lt;code&gt;inert&lt;/code&gt; + &lt;code&gt;aria-hidden&lt;/code&gt; via MutationObserver
&lt;/h3&gt;

&lt;p&gt;Radix uses the &lt;code&gt;aria-hidden&lt;/code&gt; package to enforce accessibility isolation. It sets &lt;code&gt;aria-hidden="true"&lt;/code&gt; and the HTML &lt;code&gt;inert&lt;/code&gt; attribute on &lt;strong&gt;every direct child of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; that is not the active dialog portal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The brutal part: it uses its own &lt;code&gt;MutationObserver&lt;/code&gt; to &lt;strong&gt;continuously re-apply&lt;/strong&gt; these attributes whenever new children are added to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Google Places appends &lt;code&gt;.pac-container&lt;/code&gt; dynamically when suggestions load. The observer sees it, and within milliseconds, locks it down again. You can't out-run it with a one-time fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3 — "Outside" Click Dismissal
&lt;/h3&gt;

&lt;p&gt;Even if you get past layers 1 and 2, there's a third trap. When the user clicks a suggestion inside &lt;code&gt;.pac-container&lt;/code&gt;, Radix sees a pointer event originating outside the dialog bounds. It interprets this as an outside interaction and &lt;strong&gt;closes the modal before the selection can register&lt;/strong&gt;. The modal snaps shut. Nothing is selected. The user has to start over.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3-Part Fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Part 1 — CSS: Restore Visibility and Pointer Events
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* globals.css */&lt;/span&gt;

&lt;span class="c"&gt;/*
 * Fix: Google Places Autocomplete inside Radix UI modal dialogs.
 *
 * z-index: 9999 renders the dropdown above the Radix overlay (z-50).
 * pointer-events: auto overrides the body-level pointer-events: none.
 */&lt;/span&gt;
&lt;span class="nc"&gt;.pac-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles Layer 1. The dropdown is now visible and technically interactive — but still frozen with &lt;code&gt;inert&lt;/code&gt;. On its own, this fix accomplishes nothing useful.&lt;/p&gt;




&lt;h3&gt;
  
  
  Part 2 — &lt;code&gt;requestAnimationFrame&lt;/code&gt; Loop: Win the War Against &lt;code&gt;aria-hidden&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The instinct here is to use a &lt;code&gt;MutationObserver&lt;/code&gt; to strip &lt;code&gt;inert&lt;/code&gt; whenever it's applied. That won't work. The &lt;code&gt;aria-hidden&lt;/code&gt; package has its &lt;strong&gt;own&lt;/strong&gt; observer that immediately re-applies it. Your observer and theirs enter a race condition — and yours will lose frames.&lt;/p&gt;

&lt;p&gt;The solution is &lt;code&gt;requestAnimationFrame&lt;/code&gt;. It fires before every paint, meaning the dropdown is always clean and interactive by the time the user sees it — regardless of what &lt;code&gt;aria-hidden&lt;/code&gt;'s observer does between frames.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unblock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.pac-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unblock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unblock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;"Isn't a RAF loop expensive?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. &lt;code&gt;querySelectorAll&lt;/code&gt; on a class selector is near-instant. Removing a non-existent attribute is a no-op. The loop only runs while the dialog is open and cleans up automatically on close. In practice, the performance footprint is negligible.&lt;/p&gt;




&lt;h3&gt;
  
  
  Part 3 — &lt;code&gt;onInteractOutside&lt;/code&gt;: Stop Radix From Closing the Dialog on Selection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleInteractOutside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.pac-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;Wire it to your &lt;code&gt;DialogContent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DialogContent&lt;/span&gt; &lt;span class="na"&gt;onInteractOutside&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleInteractOutside&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Google Places Autocomplete input */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DialogContent&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;Clicking a suggestion is no longer treated as an outside interaction. The dialog holds open. The selection fires correctly. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Copy-Paste Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Add to your global CSS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.pac-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&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;&lt;strong&gt;Step 2 — Drop this into your dialog component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Dialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DialogContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DialogTrigger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@radix-ui/react-dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AddressFormDialog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOpen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Part 2: Strip inert/aria-hidden every frame while the dialog is open&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unblock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.pac-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unblock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unblock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Part 3: Prevent dialog dismissal when clicking a Places suggestion&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleInteractOutside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.pac-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onOpenChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setOpen&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DialogTrigger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Edit Address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DialogTrigger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DialogContent&lt;/span&gt; &lt;span class="na"&gt;onInteractOutside&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleInteractOutside&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Your Google Places Autocomplete input here */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DialogContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Why Every Other "Fix" Falls Short
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Common suggestion&lt;/th&gt;
&lt;th&gt;Why it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Just add &lt;code&gt;z-index&lt;/code&gt; to &lt;code&gt;.pac-container&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Doesn't touch &lt;code&gt;inert&lt;/code&gt; or &lt;code&gt;pointer-events&lt;/code&gt; — dropdown is still frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use &lt;code&gt;modal={false}&lt;/code&gt; on the Dialog&lt;/td&gt;
&lt;td&gt;Disables focus trapping and dismissal — breaks accessibility entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portal the Autocomplete manually&lt;/td&gt;
&lt;td&gt;Google Places manages its own DOM insertion — you can't control it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MutationObserver&lt;/code&gt; to remove &lt;code&gt;inert&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Races with &lt;code&gt;aria-hidden&lt;/code&gt;'s own observer and consistently loses frames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only handle &lt;code&gt;onPointerDownOutside&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Misses keyboard selection; doesn't address &lt;code&gt;inert&lt;/code&gt; at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Compatibility
&lt;/h2&gt;

&lt;p&gt;This fix works with all of the following — no modifications needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@radix-ui/react-dialog&lt;/code&gt; (any version)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@react-google-maps/api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@googlemaps/react-wrapper&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Raw &lt;code&gt;google.maps.places.Autocomplete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Radix &lt;code&gt;AlertDialog&lt;/code&gt;, &lt;code&gt;Sheet&lt;/code&gt;, &lt;code&gt;Drawer&lt;/code&gt;, and any component built on &lt;code&gt;DismissableLayer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;This bug exists because two well-engineered libraries make opposing assumptions about the DOM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Radix&lt;/strong&gt; assumes it owns &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; when a modal is open and aggressively enforces that contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Places&lt;/strong&gt; assumes it can freely append to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; and have those elements be interactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither library is wrong on its own terms. But when they meet, the result is a silent failure that's nearly impossible to debug without understanding both systems deeply.&lt;/p&gt;

&lt;p&gt;The three-part fix isn't a hack — it's a precise surgical response to each layer of the conflict. CSS restores visibility. RAF wins the frame race. &lt;code&gt;onInteractOutside&lt;/code&gt; protects the selection. Remove any one part and the bug comes back.&lt;/p&gt;

</description>
      <category>radixui</category>
      <category>googlemaps</category>
      <category>frontend</category>
      <category>debugging</category>
    </item>
    <item>
      <title>How to Create a Flipping Card Animation Using Framer Motion</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Sun, 22 Dec 2024 08:59:10 +0000</pubDate>
      <link>https://forem.com/graciesharma/how-to-create-a-flipping-card-animation-using-framer-motion-5djh</link>
      <guid>https://forem.com/graciesharma/how-to-create-a-flipping-card-animation-using-framer-motion-5djh</guid>
      <description>&lt;p&gt;Link video : &lt;a href="https://www.kapwing.com/w/WNcaT5X4jx" rel="noopener noreferrer"&gt;Flipping Card&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Framer Motion is a popular library for creating smooth and customizable animations in React. In this blog, we’ll walk through the steps to create a visually appealing flipping card animation. This card will flip between its front and back sides at regular intervals, using Framer Motion's powerful features.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
Before diving into the code, ensure you have the following:&lt;/p&gt;

&lt;p&gt;Basic knowledge of React.&lt;br&gt;
A React project set up (using tools like Create React App or Next.js).&lt;br&gt;
Framer Motion installed in your project. &lt;br&gt;
You can install it with:&lt;br&gt;
&lt;code&gt;npm install framer-motion&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";

import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import CardFront from "./card-front";
import CardBack from "./card-back";

const FlippingCard = () =&amp;gt; {
  const [isFlipped, setIsFlipped] = useState(false);

  useEffect(() =&amp;gt; {
    const interval = setInterval(() =&amp;gt; {
      setIsFlipped((prev) =&amp;gt; !prev);
    }, 2000);

    return () =&amp;gt; clearInterval(interval);
  }, []);

  return (
    &amp;lt;motion.div
      className="card-container"
      style={{
        width: "454px",
        height: "271px",
        perspective: "1000px", // Adds depth for 3D animation
      }}
    &amp;gt;
      &amp;lt;motion.div
        className="card"
        animate={{ rotateY: isFlipped ? 180 : 0 }} // Animates the flip
        transition={{ duration: 1 }} // Controls the flip speed
        style={{
          width: "100%",
          height: "100%",
          position: "relative",
          transformStyle: "preserve-3d", // Enables 3D effect
        }}
      &amp;gt;
        {/* Front Side */}
        &amp;lt;motion.div
          className="card-front"
          style={{
            position: "absolute",
            backfaceVisibility: "hidden", // Ensures only one side is visible
            width: "100%",
            height: "100%",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        &amp;gt;
          &amp;lt;CardFront /&amp;gt;
        &amp;lt;/motion.div&amp;gt;

        {/* Back Side */}
        &amp;lt;motion.div
          className="card-back"
          style={{
            position: "absolute",
            backfaceVisibility: "hidden",
            transform: "rotateY(180deg)", // Flips the back face
            width: "100%",
            height: "100%",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        &amp;gt;
          &amp;lt;CardBack /&amp;gt;
        &amp;lt;/motion.div&amp;gt;
      &amp;lt;/motion.div&amp;gt;
    &amp;lt;/motion.div&amp;gt;
  );
};

export default FlippingCard;

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

&lt;/div&gt;



&lt;p&gt;Key Features of This Component&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3D Effect with Perspective:&lt;/strong&gt;The perspective property in the outer container ensures the flipping motion feels realistic by adding depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Rotation:&lt;/strong&gt; The rotateY property toggles between 0 (front) and 180 (back) degrees to create the flip animation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smooth Animation:&lt;/strong&gt; The transition property in Framer Motion controls the flip speed and smoothness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Flip:&lt;/strong&gt; A setInterval hook toggles the isFlipped state every 2 seconds for continuous flipping. You can replace this with user-triggered events like onClick or onHover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backface Visibility:&lt;/strong&gt;  &lt;code&gt;backfaceVisibility: hidden&lt;/code&gt; ensures that only the visible side is displayed while flipping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Customize the Flipping Card&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content:&lt;/strong&gt; Replace CardFront and CardBack components with custom designs or dynamic content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimensions:&lt;/strong&gt; Modify width and height in the styles to fit your design needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transition Speed:&lt;/strong&gt; Adjust the duration in the transition property for faster or slower flips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flip Trigger:&lt;/strong&gt; Replace the setInterval with an event handler.&lt;/p&gt;

</description>
      <category>flippingcard</category>
      <category>framermotion</category>
      <category>animation</category>
      <category>react</category>
    </item>
    <item>
      <title>Implementing a QR Code Download Feature in React</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Tue, 07 May 2024 04:17:10 +0000</pubDate>
      <link>https://forem.com/graciesharma/implementing-a-qr-code-download-feature-in-react-3p7i</link>
      <guid>https://forem.com/graciesharma/implementing-a-qr-code-download-feature-in-react-3p7i</guid>
      <description>&lt;p&gt;In this blog post, I will demonstrate how to implement a QR code download React application. This tutorial assumes that you have a basic understanding of React. It follows the steps involved in building a component named DownloadQR, responsible for orchestrating the download of a QR image file at the user’s click request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import React, { useState } from "react";

// Constant URL for the QR code image
const qrCodeDownload = "https://yourserver.com/path/to/qr";

const DownloadQR = () =&amp;gt; {
  const [error, setError] = useState("");

  const handleDownload = async () =&amp;gt; {
    try {
      const response = await fetch(qrCodeDownload);
      const blob = await response.blob();
      const downloadUrl = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = downloadUrl;
      a.download = "AH_Campaign_QRCode.png";
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(downloadUrl);
    } catch (err) {
      setError("Failed to download QR Code. Please try again.");
    }
  };

  return (
    &amp;lt;&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p styles={{ color: "red" }}&amp;gt;{error}&amp;lt;/p&amp;gt;}
      &amp;lt;button onClick={handleDownload}&amp;gt;
        Download QR
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default DownloadQR;

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

&lt;/div&gt;



&lt;p&gt;Key components include the:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;useState Hook for Error Handling&lt;/strong&gt;&lt;br&gt;
We employ useState to handle error messages in the component. When the download fails, we can notify the user of what went wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;handleDownload function&lt;/strong&gt;&lt;br&gt;
This is an event handler for the click event on the download button. Firstly, it tries to obtain the QR code image from a predefined URL (qrCodeDownload). A blob is then generated from the downloaded image. After that, we create a temporary URL for the blob. We make use of URL.createObjectURL to generate a URL for the blob, turning it into a file-like object. Then we create a temporary anchor tag &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;,  assign the generated URL to the href attribute, and set the download attribute to the desired file name. Finally, we simulate a click event on the manipulatively created anchor tag and remove the anchor tag from the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;br&gt;
In the event that the fetch is rejected at any point in the process during the handleDownload function call fails due to some error in the URL or the server is unreachable, an exception will be captured in the catch block, updating the error message in the state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI elements&lt;/strong&gt;&lt;br&gt;
The final component consists of a simple button that the user can click to initiate the download. An error message is shown above the button if an error has been dispatched.&lt;/p&gt;

&lt;p&gt;Once you have done that, Test Your Component Your component should be tested to account for the following scenarios, once you have integrated the DownloadQR component into your existing codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The QR code is successfully downloaded from the server.&lt;/li&gt;
&lt;li&gt;The server request fails due to an error in the server or network.&lt;/li&gt;
&lt;li&gt;The URL pointing to the QR code image is incorrect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This utility component improves user engagement by providing a download feature that enables direct downloads of QR codes. This functionality can be beneficial on digital marketing platforms where QR codes are used for campaigns or for activities that demand the scanning of QR codes, such as event registrations. Besides, it highlights how one can handle asynchronous operations and errors effectively in web applications using React.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>qrcodedownload</category>
      <category>qrcode</category>
    </item>
    <item>
      <title>Implementing CSV Data Export in React Without External Libraries</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Fri, 26 Apr 2024 13:21:47 +0000</pubDate>
      <link>https://forem.com/graciesharma/implementing-csv-data-export-in-react-without-external-libraries-3030</link>
      <guid>https://forem.com/graciesharma/implementing-csv-data-export-in-react-without-external-libraries-3030</guid>
      <description>&lt;p&gt;In the realm of React development, exporting data in CSV format is a frequent requirement. This task can be accomplished without the dependency on external libraries by leveraging vanilla JavaScript capabilities within a React component. This guide details the process of creating a feature that enables users to export CSV files directly from a React application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Construct a React Component
&lt;/h2&gt;

&lt;p&gt;Begin by crafting a straightforward React component dedicated to managing the CSV export process. This component should include a button to initiate the export and a function to handle the creation and downloading of the CSV file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';

const ExportCSV = ({ data, fileName }) =&amp;gt; {
  const downloadCSV = () =&amp;gt; {
    // Convert the data array into a CSV string
    const csvString = [
      ["Header1", "Header2", "Header3"], // Specify your headers here
      ...data.map(item =&amp;gt; [item.field1, item.field2, item.field3]) // Map your data fields accordingly
    ]
    .map(row =&amp;gt; row.join(","))
    .join("\n");

    // Create a Blob from the CSV string
    const blob = new Blob([csvString], { type: 'text/csv' });

    // Generate a download link and initiate the download
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName || 'download.csv';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  };

  return &amp;lt;button onClick={downloadCSV}&amp;gt;Export CSV&amp;lt;/button&amp;gt;;
};

export default ExportCSV;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Deploy the Component
&lt;/h2&gt;

&lt;p&gt;Incorporate the ExportCSV component into another component or page within your application where you need the CSV export functionality. Supply the necessary data and the desired filename for the exported file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import ExportCSV from './ExportCSV';

const MyComponent = () =&amp;gt; {
  const data = [
    { field1: "row1-col1", field2: "row1-col2", field3: "row1-col3" },
    { field1: "row2-col1", field2: "row2-col2", field3: "row2-col3" }
    // Include additional data as needed
  ];

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;My Data Exporter&amp;lt;/h1&amp;gt;
      &amp;lt;ExportCSV data={data} fileName="myData.csv" /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default MyComponent;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detailed Workflow Explanation:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generate CSV String:&lt;/strong&gt; The function transforms an array of data into a CSV format, starting with headers and followed by the corresponding data rows.&lt;br&gt;
&lt;strong&gt;Blob and URL Handling:&lt;/strong&gt; A Blob object is created using the CSV string to facilitate the file download.&lt;br&gt;
&lt;strong&gt;Dynamic Link for Download:&lt;/strong&gt; The download process is triggered by creating a temporary anchor (&lt;code&gt;a&lt;/code&gt;) element programmatically and assigning the Blob URL to it. This is followed by a simulated click to commence the download.&lt;br&gt;
&lt;strong&gt;Cleanup Post-Download:&lt;/strong&gt; The Blob URL is revoked, and the anchor element is removed to release resources and clean up the document.&lt;/p&gt;

&lt;p&gt;The technique can be expanded to include more complex CSV formatting options, handle diverse data types, or introduce error handling mechanisms to enhance the robustness and functionality of your application.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>exportascsv</category>
    </item>
    <item>
      <title>Async and Await</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Mon, 18 Dec 2023 14:11:53 +0000</pubDate>
      <link>https://forem.com/graciesharma/async-and-await-137</link>
      <guid>https://forem.com/graciesharma/async-and-await-137</guid>
      <description>&lt;p&gt;Hey there! 🌟 Today, let's talk about something cool that will make your coding adventures even more awesome: &lt;code&gt;async and await.&lt;/code&gt; Don't worry if these terms sound like fancy tech jargon – we're here to make them as simple as playing your favorite game!&lt;/p&gt;

&lt;p&gt;Imagine Your Code as a Story&lt;br&gt;
Picture your code as a storybook. In traditional coding (synchronous), it reads like a story from start to finish. But what if we want our story to do a few things at once, like drawing and playing music? That's where &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; come in to make our story more exciting!&lt;/p&gt;

&lt;p&gt;Meet &lt;code&gt;async&lt;/code&gt; – The Storytelling Wizard&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt; Function:&lt;br&gt;
Think of an async function like a wizard in your story. It's special because it can do magic (or code) while the rest of your story keeps going.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
async function myAsyncFunction() {
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter &lt;code&gt;await&lt;/code&gt; – The Pause Button&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await&lt;/code&gt; Operator:&lt;br&gt;
await is like a pause button in your story. It tells your wizard (the async function) to wait for something to finish before moving on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function fetchData() {
  let response = await fetch('https://api.example.com/data');
  let data = await response.json();
  console.log(data);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why You'll Love Them&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No More Code Confusion:&lt;/strong&gt; Remember when you had to jump around your storybook (code) because of callbacks? async and await make your story flow smoothly, like reading one page at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easy-to-Read Code:&lt;/strong&gt; Your code becomes like a simple story you can read step by step. It's like telling your computer, "Hey, do this, then do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing Mistakes Made Easy:&lt;/strong&gt; Mistakes happen, but with async and await, you can catch and fix them just like finding a hidden treasure in your story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Super-Fast Stories:&lt;/strong&gt; With async and await, your story can do multiple things at once, making it as fast and exciting as your favorite game!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's Wrap It Up, Storyteller!&lt;/strong&gt;&lt;br&gt;
So, async and await are your buddies, making your code more fun and adventurous. They help your story (code) do cool things without getting all tangled up. Keep playing with them! Happy Learning :D&lt;/p&gt;

</description>
      <category>async</category>
      <category>await</category>
      <category>javascript</category>
    </item>
    <item>
      <title>React Error Boundary</title>
      <dc:creator>graciesharma</dc:creator>
      <pubDate>Tue, 28 Feb 2023 15:01:11 +0000</pubDate>
      <link>https://forem.com/graciesharma/react-error-boundary-28n1</link>
      <guid>https://forem.com/graciesharma/react-error-boundary-28n1</guid>
      <description>&lt;p&gt;React Error Boundaries are a feature introduced in React that allows you to catch errors that occur during rendering, in life cycle methods, and in constructors of the entire component tree. With error boundaries, you can prevent an error from crashing the entire application and instead, display a fallback UI. Error boundaries are currently only supported as class components in React, so if you're using hooks, you may need to include a class component for error handling.&lt;/p&gt;

&lt;p&gt;Error Boundaries work by wrapping the components that you want to monitor for errors. When an error occurs within one of these components or any of its descendants, the Error Boundary component catches the error and displays a fallback UI instead of the crashed component tree.&lt;/p&gt;

&lt;p&gt;To catch errors during rendering, wrap the component you want to handle errors with an Error Boundary component. Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ErrorBoundary errorComponent={ClientError}&amp;gt;
                &amp;lt;Component {...pageProps} /&amp;gt;
              &amp;lt;/ErrorBoundary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, if an error occurs during rendering , the Error Boundary component will catch the error and display the fallback UI instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { Component, ErrorInfo } from "react";

interface ErrorBoundaryProps {
  errorComponent: () =&amp;gt; JSX.Element;
  children: React.ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
}

class ErrorBoundary extends Component&amp;lt;ErrorBoundaryProps, ErrorBoundaryState&amp;gt; {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = {
      hasError: false,
    };
  }

  static getDerivedStateFromError(error: unknown) {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    //TODO: Integrate Datadog
    console.error("Error Boundary Caught an error: ", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return &amp;lt;this.props.errorComponent /&amp;gt;;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

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

&lt;/div&gt;



&lt;p&gt;In the example above,there is a React component called ErrorBoundary. Its purpose is to catch any errors that occur within its child components and display a fallback UI instead of the crashed component tree.&lt;/p&gt;

&lt;p&gt;The ErrorBoundary class has two interfaces. The &lt;strong&gt;ErrorBoundaryProps&lt;/strong&gt; interface defines the props that ErrorBoundary accepts, which includes an errorComponent prop and a children prop. The &lt;strong&gt;ErrorBoundaryState&lt;/strong&gt; interface defines the state of the ErrorBoundary, which is whether or not an error has occurred.&lt;/p&gt;

&lt;p&gt;The ErrorBoundary class has a constructor that initializes the state to &lt;em&gt;hasError: false.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The ErrorBoundary class also has two methods to handle errors. The static &lt;strong&gt;getDerivedStateFromError()&lt;/strong&gt;method is called when an error is thrown from any of its child components. It returns an object that updates the state of the ErrorBoundary to indicate that an error has occurred.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;componentDidCatch()&lt;/strong&gt; method is called after an error is thrown from any of its child components. In this method, the developer can add any code necessary to handle the error, such as logging or reporting the error to an external service. In this code snippet, the method logs the error and errorInfo to the console.&lt;/p&gt;

&lt;p&gt;Finally, the render() method checks if an error has occurred by looking at the hasError state. If an error has occurred, it renders the errorComponent that was passed in as a prop. If no error has occurred, it renders its children prop, which is the child component tree that it is wrapping.&lt;/p&gt;

&lt;p&gt;This component is very useful for catching errors in production environments.It’s generally advisable to use at least one Error Boundary at the root of your app (e.g., the App.js file). This will prevent users from seeing a blank HTML page and perhaps see a nice/custom fallback UI instead.&lt;/p&gt;

&lt;p&gt;Remember, just like in life, sometimes things can go wrong in our React applications too. But with the ErrorBoundary, we can catch those errors and make sure they don't bring the whole house down! So go ahead and wrap your components with an ErrorBoundary, and rest assured that even if your code throws some shade, your application will keep on shining!;) &lt;/p&gt;

</description>
      <category>developer</category>
      <category>android</category>
      <category>frontend</category>
      <category>vibecoding</category>
    </item>
  </channel>
</rss>
