<?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: Ashish Verma</title>
    <description>The latest articles on DEV Community by Ashish Verma (@ashish_verma_0f878dafa2cf).</description>
    <link>https://dev.to/ashish_verma_0f878dafa2cf</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%2F4129975%2F47996f76-c1fa-4a4f-8237-c908f8c47f6f.png</url>
      <title>DEV Community: Ashish Verma</title>
      <link>https://dev.to/ashish_verma_0f878dafa2cf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashish_verma_0f878dafa2cf"/>
    <language>en</language>
    <item>
      <title>Building a Real-Time Cricket Scorecard Library in React: Handling High-Frequency WebSockets at 60 FPS</title>
      <dc:creator>Ashish Verma</dc:creator>
      <pubDate>Tue, 22 Sep 2026 12:27:18 +0000</pubDate>
      <link>https://dev.to/ashish_verma_0f878dafa2cf/building-a-real-time-cricket-scorecard-library-in-react-handling-high-frequency-websockets-at-60-1ckj</link>
      <guid>https://dev.to/ashish_verma_0f878dafa2cf/building-a-real-time-cricket-scorecard-library-in-react-handling-high-frequency-websockets-at-60-1ckj</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Live cricket scores update fast — sometimes multiple events land in the same animation frame. Wire that straight into &lt;code&gt;setState&lt;/code&gt; and you get wasted renders, visible jank, and a UI that quietly falls behind the real match. Most sports scoreboard code I found on GitHub doesn't handle this at all — it just re-renders on every socket message and hopes for the best.&lt;/p&gt;

&lt;p&gt;I wanted to see what it actually takes to build one that doesn't do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Batches, doesn't just receive.&lt;/strong&gt; Every incoming WebSocket packet gets pushed into a buffer, and a single &lt;code&gt;requestAnimationFrame&lt;/code&gt; callback flushes the whole buffer into one state commit — right before the browser paints. Ten events arriving in the same frame become one render, not ten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;De-duplicates by ID.&lt;/strong&gt; Reconnects happen. Servers replay events. A &lt;code&gt;processedIds&lt;/code&gt; set (capped and FIFO-evicted so it can't leak memory) means a re-sent packet never gets applied twice — checked at push-time, not flush-time, so even two duplicates arriving in the same frame before a flush can't slip through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never trusts the feed blindly.&lt;/strong&gt; A malformed packet gets caught and skipped, not left to crash the whole batch. Once an innings ends — 10 wickets, the overs limit, or a chased-down target — the ingestion engine ignores every further packet, so a feed that keeps sending late events after a match is over can't reopen it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gets the actual cricket rules right.&lt;/strong&gt; This was the part that took longer than expected: standard &lt;code&gt;2.3&lt;/code&gt;-overs notation (not a decimal fraction — 2 completed overs, 3 balls into the next), a full second-innings target chase where reaching the target ends the match &lt;em&gt;immediately&lt;/em&gt;, even mid-over, and an accurate result — win by runs, win by wickets, or a tie — computed from the actual match state rather than guessed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't own anything it doesn't have to.&lt;/strong&gt; It never opens the WebSocket connection — you bring your own socket, your own reconnect logic, your own auth. It never calls an AI provider either: there's a &lt;code&gt;generateSummary&lt;/code&gt; prop that accepts any function (sync or async) and just renders whatever text comes back. Same philosophy both times — accept a result, don't own the mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolated by construction.&lt;/strong&gt; Each &lt;code&gt;&amp;lt;LiveCricketScorecard&amp;gt;&lt;/code&gt; gets its own store via React Context, created once with a lazy &lt;code&gt;useState&lt;/code&gt; initializer (not &lt;code&gt;useMemo&lt;/code&gt;, which turned out to have a real bug: it silently reset the entire match on every parent re-render if the caller didn't memoize &lt;code&gt;initialData&lt;/code&gt; — which is the default case for most usage). Two scorecards on one page genuinely can't interfere with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;rAF-batched ingestion with dedup and malformed-payload resilience&lt;/li&gt;
&lt;li&gt;10-wicket cap, configurable overs limit, full second-innings target chase&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;onInningsComplete&lt;/code&gt; callback and an AI-extensibility hook (&lt;code&gt;generateSummary&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-live&lt;/code&gt; regions for ball-by-ball updates and match results&lt;/li&gt;
&lt;li&gt;13 tests covering the batching, dedup, and cricket-rules logic&lt;/li&gt;
&lt;li&gt;TypeScript, ESM + UMD builds, zero required CSS beyond Tailwind&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-sports-scorecard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://react-sports-scorecard.vercel.app/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt; — a simulated feed, a second-match spawner to see the isolation directly, and a real target chase you can play through&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aashishverma112/react-sports-scorecard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — the ingestion engine specifically is in &lt;code&gt;src/ingestion/socketIngestion.ts&lt;/code&gt; if you want to see how the batching/dedup logic works&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/react-sports-scorecard" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cricket-only for now, MIT licensed. Issues and PRs welcome — I'm particularly interested in what breaks under a real production feed, since everything here has only been tested against a simulated one.&lt;/p&gt;

</description>
      <category>react</category>
      <category>showdev</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
