Daniel Eden, Designer

Building a Coin-Tossing Simulator with React, Hooks, and Vercel: Part 2

In the first part of this tutorial, we built a simple React app with a button to toss a coin and tell us which side it landed on.

(You can see the source code of where we left off here and run the app here)

In this second part, we’re going to start tracking the number of times each side appears, have the coin tossed automatically for us (rather than having to press a button) and visualise the results over time.

Counting Each Side

Currently, our app tracks the total number of times the coin has been tossed, but you’ll remember that from the start, we wanted to know how many times each side has appeared. Let’s change our code to show us that information.

In App.js, we’ll add a few new variables using useState:

function App() {
    const [side, setSide] = useState(1)
    const [heads, setHeads] = useState(0)
    const [tails, setTails] = useState(0)

    const tossed = heads + tails

    const tossCoin = () => {
        const landedOn = Math.round(Math.random())

        if (landedOn === 1) {
            setHeads(heads + 1)
        } else {
            setTails(tails + 1)
        }

        setSide(landedOn)
    }

    return (
        <div>
            <p>The coin has been tossed {tossed} times.</p>
            <p>It landed on {side === 1 ? "heads" : "tails"}</p>

            <ul>
                <li>Heads: {heads}</li>
                <li>Tails: {tails}</li>
            </ul>

            <button onClick={tossCoin}>Toss coin</button>
        </div>
    )
}

We’ve added four new variables, heads, setHeads, tails, and setTails, to get and set the number of times that heads and tails each appear.

We’ve removed the useState function call that sets tossed and setTossed and instead we’ll set const tossed = heads + tails, just to reduce the number of “sources of truth” for the total number of times the coin has been tossed.

We’ve also added a ul element to the rendered output where we can see the values of heads and tails, but just showing a number isn’t very interesting.

Visualising The Coin Tosses

Thankfully, there’s a native HTML element that is perfectly suited for showing the number of times each side appears out of a total: the meter element.

We use the meter element like so:

<meter value="50" max="100" />

This would render a meter that’s filled halfway. Let’s use this element to display how many times each side has landed inside the ul we added earlier:

<ul>
    <li>
        <label htmlFor="heads">Heads: {heads}</label>
        <meter id="heads" value={heads} max={tossed} />
    </li>
    <li>
        <label htmlFor="tails">Tails: {tails}</label>
        <meter id="tails" value={tails} max={tossed} />
    </li>
</ul>

Automatically Tossing The Coin

The final step in this part of the tutorial will be to have the coin tossed automatically. To do this, we’ll use another React hook: useEffect.

The useEffect hook is used to call a function whenever a component renders or is re-rendered (which happens if its properties—props—or state changes). This is a bit abstract, but it’s useful to us: we want to call a function when our app renders so that we can toss the coin automatically.

In App.js, we’ll get the useEffect hook the same way we get the useState hook:

const { useEffect, useState } = React

And we’ll add a useEffect call to our App component. We can add this right before the return statement:

...

useEffect(() => {
  const interval = setInterval(tossCoin, 500)
  return () => clearInterval(interval)
})

return (
  <div>
    ...

This will cause the tossCoin function to be called every 500 milliseconds, or every ½ a second. Note that in the useEffect call, we return a function that clears the interval: this is basically to clean up after ourselves if the App stops rendering, since we don’t then need to keep calling tossCoin every 500ms, and doing so could actually lead to bugs or slowness.

You can change the interval to a different value to have the coin tossed more or less frequently, but it’s already very useful: now that the coin is being tossed automatically, you can see within just a few seconds that the frequency of each side landing averages out to be about even quite quickly!

Let’s make one more change. Now that the coin is tossed automatically, the “toss coin” button is a bit useless. Let’s update our app so that there’s a “pause” button that lets us momentarily stop the automatic tossing and manually toss the coin again.

function App() {
    const [side, setSide] = useState(1)
    const [heads, setHeads] = useState(0)
    const [tails, setTails] = useState(0)
    const [isPaused, setIsPaused] = useState(false)

    const tossed = heads + tails

    const tossCoin = () => {
        const landedOn = Math.round(Math.random())

        if (landedOn === 1) {
            setHeads(heads + 1)
        } else {
            setTails(tails + 1)
        }

        setSide(landedOn)
    }

    useEffect(() => {
        const interval = setInterval(() => {
            if (!isPaused) {
                tossCoin()
            }
        }, 10)
        return () => clearInterval(interval)
    })

    return (
        <div>
            <p>The coin has been tossed {tossed} times.</p>
            <p>It landed on {side === 1 ? "heads" : "tails"}</p>
            <ul>
                <li>
                    <label htmlFor="heads">Heads: {heads}</label>
                    <meter id="heads" value={heads} max={tossed} />
                </li>
                <li>
                    <label htmlFor="tails">Tails: {tails}</label>
                    <meter id="tails" value={tails} max={tossed} />
                </li>
            </ul>

            <button onClick={() => setIsPaused(!isPaused)}>
                {!isPaused ? "Pause" : "Continue"}
            </button>
            {isPaused && <button onClick={tossCoin}>Toss coin</button>}
        </div>
    )
}

The complete app code is seen above, with the changes necessary to add the “pause” button. Let’s walk through them together.

We’ve added an isPaused and setIsPaused variable to determine whether the app is running, and set it to false by default.

We’ve changed the useEffect callback a bit:

useEffect(() => {
    const interval = setInterval(() => {
        if (!isPaused) {
            tossCoin()
        }
    }, 10)
    return () => clearInterval(interval)
})

Now, we check if isPaused is false before calling the tossCoin function.

Finally, in our rendered output, we add the new “Pause” button, which toggles the isPaused variable, and only show the “toss coin” button if the isPaused variable is false (since !true is equal to false):

(
<button onClick={() => setIsPaused(!isPaused)}>
  {!isPaused ? "Pause" : "Continue"}
</button>
{ isPaused && <button onClick={tossCoin}>Toss coin</button> }
)

And that’s it! Now our app will automatically toss the coin for us, and show us a nice visualisation of the number of times each side is shown.

In the next and final part of this tutorial, we’ll keep track of the patterns that emerge—the number of times that each side shows consecutively—and we’ll publish our app publicly using a service called Vercel.