<?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: Kane</title>
    <description>The latest articles on DEV Community by Kane (@comzzycomzzy).</description>
    <link>https://dev.to/comzzycomzzy</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%2F4078652%2F402d0c65-31a6-4713-950d-8ff8c985d116.png</url>
      <title>DEV Community: Kane</title>
      <link>https://dev.to/comzzycomzzy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/comzzycomzzy"/>
    <language>en</language>
    <item>
      <title>StableRoute Frontend: Resilient Polling with Adaptive Exponential Backoff</title>
      <dc:creator>Kane</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:30:04 +0000</pubDate>
      <link>https://dev.to/comzzycomzzy/stableroute-frontend-resilient-polling-with-adaptive-exponential-backoff-40g7</link>
      <guid>https://dev.to/comzzycomzzy/stableroute-frontend-resilient-polling-with-adaptive-exponential-backoff-40g7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;StableRoute Frontend is the web interface for StableRoute, an open-source decentralized liquidity routing protocol built on the Stellar network. The application is built with Next.js (App Router), React, and TypeScript.&lt;br&gt;
The platform provides a comprehensive suite of tools for traders, liquidity providers, and developers:&lt;br&gt;
Payment and Liquidity Routing: An interactive interface for querying optimal swap and cross-currency path routing quotes across Stellar asset pools.&lt;br&gt;
Liquidity Pool Management: Workflows to register, inspect, and configure supported asset pairs.&lt;br&gt;
Real-Time Protocol Monitoring: A live status dashboard that polls protocol health, operational state (active vs. paused), and liquidity pool counts.&lt;br&gt;
Developer and Infrastructure Controls: Management consoles for generating and revoking API keys, registering webhook event subscribers, viewing immutable system audit logs, and inspecting OpenAPI documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In src/app/stats/Client.tsx, dashboard statistics were updated using a raw setInterval:useEffect(() =&amp;gt; {&lt;br&gt;
  const id = setInterval(refetch, POLL_MS);&lt;br&gt;
  return () =&amp;gt; clearInterval(id);&lt;br&gt;
}, [refetch]);&lt;/p&gt;

&lt;p&gt;This caused three critical issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Overlapping In-Flight Requests (Race Conditions)
setInterval fired strictly on a 5-second timer regardless of how long the network request took. If a request stalled or took 6 seconds due to network lag, a second request was fired before the first one finished. This created stacked concurrent requests where an older, delayed response could overwrite newer data in React state.&lt;/li&gt;
&lt;li&gt;Backend Hammering During Outages (Thundering Herd)
When the backend experienced downtime, high load, or returned errors (500s/429s), the frontend never backed off. It continued hammering the failing endpoint every 5 seconds unabated, causing rate limits and thrashing the UI with re-rendering error alerts.&lt;/li&gt;
&lt;li&gt;Timer Churn on Re-render
Because refetch was in the dependency array, whenever parent component updates created a new function reference, the old interval was cleared and a new one started from scratch, disrupting regular polling cadence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How I Fixed It&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I replaced setInterval with a custom, lifecycle-aware scheduling hook called useBackoffInterval:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Settled-State Scheduling
Instead of a blind clock, the hook waits for the request to settle into either 'success' or 'error' before scheduling the next timer. While a request is in the 'loading' state, no timers run, completely eliminating overlapping requests.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exponential Backoff on Failures&lt;br&gt;
We track consecutive failures using a ref (failureCountRef). When errors occur, the delay doubles progressively:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ⎛          failures      ⎞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;delay = min ⎝baseMs × 2        ,maxMs⎠&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This increases the retry delay from 5s to 10s, 20s, 40s, and caps at 60s, giving backend services breathing room to recover.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instant Cadence Recovery
On the very first successful response (status === 'success'), the failure counter resets to 0 and the polling interval immediately returns to the standard 5-second cadence without requiring user intervention.&lt;/li&gt;
&lt;li&gt;Ref-Stabilized Callback
We stored the callback in useRef(callback) and synced it on every render (callbackRef.current = callback). This guarantees that the timer always executes the freshest callback closure without tearing down and restarting the active timer on unrelated UI re-renders.&lt;/li&gt;
&lt;li&gt;Clean Unmount Teardown
Each scheduled timer returns a cleanup function calling clearTimeout(timeoutId), preventing memory leaks and state updates on unmounted components.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Pull Request / Branch: &lt;a href="https://github.com/comzzy-comzzy/Stableroute-frontend/tree/performance/stats-polling-backoff" rel="noopener noreferrer"&gt;https://github.com/comzzy-comzzy/Stableroute-frontend/tree/performance/stats-polling-backoff&lt;/a&gt;&lt;br&gt;
Target Repository: &lt;a href="https://github.com/StableRoute-Org/Stableroute-frontend" rel="noopener noreferrer"&gt;https://github.com/StableRoute-Org/Stableroute-frontend&lt;/a&gt;&lt;br&gt;
Commit: 786106d62e4697aa4a9b536ab75bb320101edd9b (perf(stats): back off polling interval after repeated failures)&lt;/p&gt;

&lt;p&gt;// Custom hook in src/app/stats/Client.tsx&lt;/p&gt;

&lt;p&gt;export function useBackoffInterval(&lt;br&gt;
  status: PollStatus,&lt;br&gt;
  callback: () =&amp;gt; void,&lt;br&gt;
  options: BackoffIntervalOptions = {}&lt;br&gt;
): void {&lt;br&gt;
  const {&lt;br&gt;
    baseMs = POLL_MS,          // 5,000ms&lt;br&gt;
    maxMs = MAX_POLL_MS,        // 60,000ms&lt;br&gt;
    schedule = scheduleTimeout,&lt;br&gt;
    cancel = cancelTimeout,&lt;br&gt;
  } = options;&lt;/p&gt;

&lt;p&gt;const callbackRef = useRef(callback);&lt;br&gt;
  const failureCountRef = useRef(0);&lt;br&gt;
  callbackRef.current = callback;&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
    // Only schedule next tick after a request settles&lt;br&gt;
    if (status !== 'success' &amp;amp;&amp;amp; status !== 'error') return;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (status === 'success') {
  failureCountRef.current = 0;
} else {
  failureCountRef.current += 1;
}

const delayMs =
  status === 'success'
    ? baseMs
    : Math.min(baseMs * 2 ** failureCountRef.current, maxMs);

const timeoutId = schedule(() =&amp;gt; callbackRef.current(), delayMs);
return () =&amp;gt; cancel(timeoutId);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}, [baseMs, cancel, maxMs, schedule, status]);&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Custom useBackoffInterval Hook: Instead of hacking state inside the UI component, I decoupled retry scheduling into a reusable, lifecycle-aware hook.&lt;/li&gt;
&lt;li&gt;Settled-State Gating: Scheduling only runs when the request status reaches 'success' or 'error'. While the status is 'idle' or 'loading', no timers are scheduled, strictly preventing concurrent in-flight requests.&lt;/li&gt;
&lt;li&gt;Deterministic Exponential Backoff with Upper Bound: Consecutive failures increase the delay exponentially (baseMs * 2^failures), progressing from 10s to 20s, 40s, and capping at 60s (maxMs). As soon as an update succeeds, failureCount resets to 0 and normal 5-second polling immediately resumes.&lt;/li&gt;
&lt;li&gt;Ref-Stabilized Callback Execution: Storing the refetch callback in a ref (callbackRef.current = callback) prevents timer teardown and re-creation when parent component props change across renders.&lt;/li&gt;
&lt;li&gt;Inversion of Control for Deterministic Testing: The hook accepts optional schedule and cancel functions. This allowed writing deterministic unit tests that execute synchronously without flakey time-dependent assertions.&lt;/li&gt;
&lt;li&gt;Comprehensive Unit and Integration Test Suite: Added comprehensive test coverage in src/app/stats/page.test.tsx verifying:
.Base 5-second cadence on consecutive successful responses.
.Delay doubling under network failures up to the 60-second limit.
Immediate delay reset to 5 seconds upon recovery.
.Prevention of duplicate alert renders during error backoff.
.Clean timer cancellation on component unmount.&lt;/li&gt;
&lt;li&gt;Contract Documentation: Documented the behavior, parameter options, and lifecycle guarantees in docs/hooks.md.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;Throughout the development, architecture design, debugging, and testing phases of this project and related protocol repositories, I utilized Google Antigravity paired with the Gemini 3.5 Flash (High) model as a primary engineering assistant across multiple tech stacks (React/TypeScript, Rust Soroban, Solidity, and Node.js microservices).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Primary Project: Frontend Exponential Backoff &amp;amp; Lifecycle Safety (Stableroute-frontend)
PR / Branch: performance/stats-polling-backoff (Commit 786106d)
How Google AI Was Used:
Root-Cause Diagnosis: Analyzed client network waterfall logs to isolate why client browsers flooded backend endpoints with requests during API degradation. Gemini 3.5 Flash (High) identified the unmanaged setInterval concurrency hazard.
Hook Architecture: Formulated the useBackoffInterval pattern. The model advised storing active callbacks in useRef to prevent timer re-creation on renders while gating execution strictly to settled status states ('success' | 'error').
Deterministic Test Harness: Generated the Jest mock-timer test suite (src/app/stats/page.test.tsx), verifying exact progressive delay doubling (10s, 20s, 40s, 60s cap) and unmount cleanups.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>bugsmash</category>
      <category>devchallenge</category>
      <category>performance</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
