<?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: gaven yang</title>
    <description>The latest articles on DEV Community by gaven yang (@gaven_yang_438e60f53e6ff2).</description>
    <link>https://dev.to/gaven_yang_438e60f53e6ff2</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%2F3717324%2Ffaf8d467-12f0-4221-8391-20a41b787232.jpg</url>
      <title>DEV Community: gaven yang</title>
      <link>https://dev.to/gaven_yang_438e60f53e6ff2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaven_yang_438e60f53e6ff2"/>
    <language>en</language>
    <item>
      <title>Stop Using `Math.random()` for Raffles: Building a Truly Fair Wheel with React &amp; Web Crypto API</title>
      <dc:creator>gaven yang</dc:creator>
      <pubDate>Sun, 18 Jan 2026 03:59:19 +0000</pubDate>
      <link>https://dev.to/gaven_yang_438e60f53e6ff2/stop-using-mathrandom-for-raffles-building-a-truly-fair-wheel-with-react-web-crypto-api-2ahh</link>
      <guid>https://dev.to/gaven_yang_438e60f53e6ff2/stop-using-mathrandom-for-raffles-building-a-truly-fair-wheel-with-react-web-crypto-api-2ahh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuqehn1wvmfro5kjz1xf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuqehn1wvmfro5kjz1xf.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;We've all built that simple random picker for a side project using &lt;code&gt;Math.random()&lt;/code&gt;. It's easy, it's one line of code, and it works... mostly.&lt;/p&gt;

&lt;p&gt;But when I was building &lt;strong&gt;&lt;a href="https://joyspin.xyz" rel="noopener noreferrer"&gt;JoySpin&lt;/a&gt;&lt;/strong&gt; – a tool designed for teachers and live event raffles – I realized that "mostly" wasn't good enough. If a teacher uses my tool to pick a student for a prize, or a streamer uses it to give away a gift card, the randomness needs to be &lt;strong&gt;cryptographically secure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is why &lt;code&gt;Math.random()&lt;/code&gt; fails for high-stakes picking, and how I implemented the &lt;strong&gt;Web Crypto API&lt;/strong&gt; in React to fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Pseudo-Randomness
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; is a Pseudo-Random Number Generator (PRNG). It’s fast, but it’s deterministic. If you know the internal state of the algorithm (like V8's XorShift128+), you can theoretically predict the next number.&lt;/p&gt;

&lt;p&gt;For a UI animation? Fine.&lt;br&gt;
For picking a winner? Not ideal.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Solution: &lt;code&gt;window.crypto&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To make JoySpin truly fair, I switched to &lt;code&gt;window.crypto.getRandomValues()&lt;/code&gt;. This taps into the operating system's entropy pool (mouse movements, keystrokes, thermal noise) to generate true randomness.&lt;/p&gt;

&lt;p&gt;Here is the React hook I wrote to handle the selection logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A safe random number generator function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getSecureRandomNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&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;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;maxSafeInteger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Determine the largest number that is a multiple of range&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxSafeInteger&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxSafeInteger&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;range&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;randomBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint32Array&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;randomNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;randomBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;randomNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;randomBuffer&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;randomNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Reject numbers to avoid bias (modulo bias)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;randomNumber&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;range&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;h3&gt;
  
  
  Handling the "Spin" State in React
&lt;/h3&gt;

&lt;p&gt;The logic is only half the battle. In &lt;strong&gt;JoySpin&lt;/strong&gt;, the UX is just as important. Users need to &lt;em&gt;feel&lt;/em&gt; the weight of the wheel.&lt;/p&gt;

&lt;p&gt;I separated the logic from the animation. The "Winner" is decided the moment the button is clicked (using the crypto function above), but the wheel visual is CSS logic that eases out to that specific degree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSpin&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spinning&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="c1"&gt;// 1. Decide the winner immediately using Crypto API&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winningIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSecureRandomNumber&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="nx"&gt;currentList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;winningIndex&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Calculate rotation (add extra spins for suspense)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newRotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rotation&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;currentList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;winningIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Update State&lt;/span&gt;
    &lt;span class="nf"&gt;setWinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newRotation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... animation logic&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Result: A "Trustless" Picker
&lt;/h3&gt;

&lt;p&gt;By implementing this, I can tell users that the result isn't just a JavaScript quirk—it's mathematically fair.&lt;/p&gt;

&lt;p&gt;I built this into a full production app, &lt;strong&gt;&lt;a href="https://joyspin.xyz" rel="noopener noreferrer"&gt;JoySpin.xyz&lt;/a&gt;&lt;/strong&gt;. It runs entirely in the browser (no server-side bias), supports importing lists, and includes an "Instant Elimination" mode to remove winners after they are picked.&lt;/p&gt;

&lt;p&gt;If you are building a game or a raffle tool, I highly recommend ditching &lt;code&gt;Math.random()&lt;/code&gt;. The performance cost is negligible, but the trust you gain from users is worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you ever dealt with "randomness" complaints in your apps? Let me know in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
