DEV Community

Daniel Pertu
Daniel Pertu

Posted on

The leaderboard move in our product demo is a swap between two frozen arrays, and its 20 second timer lasts 10

Our landing page runs three animated mocks of the product: a host view in a laptop frame and two phone views. No video, no screen recording, just components and a module-scope fixture.

I went back into that fixture recently for an unrelated reason and found that almost none of it does what the screen appears to be doing. None of it is a bug. All of it is the kind of thing worth knowing before you build one of these.

The clock

The host mock shows a countdown. It starts at 20 and goes to 0, with an s after it:

const timer = Math.max(0, TIME_LIMIT - elapsed)
// …
{`${timer}s`}
Enter fullscreen mode Exit fullscreen mode

TIME_LIMIT is 20. elapsed increments by 1 on an interval:

const t = setInterval(() => {
  setElapsed(p => (p >= TIME_LIMIT ? TIME_LIMIT : p + 1))
}, 500) // faster for demo
Enter fullscreen mode Exit fullscreen mode

Five hundred milliseconds. So the label counts down twenty seconds and twenty seconds take ten.

The comment is honest and the decision is right: nobody is going to watch a marketing page for twenty seconds to see one question close. The part I would not have predicted is that it is completely invisible. I had looked at that section a dozen times without noticing that the seconds were not seconds, because there is nothing on screen to compare it to. Once you know, you can count along with it, and it is obviously wrong, and it still does not matter.

The player mock has its own identical interval, in a different component, with no shared clock between them. They start on different mounts and different phase changes, so on the live page the two countdowns are simply never on the same number, which is the correct outcome: three mocks that pivot on the same beat read as one animation, and three that do not read as three things happening.

The "live" standings are two constants

The host mock's leaderboard reorders itself when a question closes. That is the thing the product actually does, and it is the thing the mock most needs to show.

Here is the whole mechanism:

const POST_Q4_SCORES = INITIAL_SCORES
  .map(e => ({ ...e, score: e.score + (Q4_POINTS[e.name] ?? 0) }))
  .sort((a, b) => b.score - a.score)
  .map((e, i) => ({ ...e, rank: i + 1 }))
Enter fullscreen mode Exit fullscreen mode

That runs once, at module load, before anything is on screen. And the render is:

{(hostPhase === "results" ? POST_Q4_SCORES : INITIAL_SCORES).map(entry => …)}
Enter fullscreen mode Exit fullscreen mode

Two arrays and a ternary. The scoreboard does not recompute, does not accumulate, and does not depend on which question the loop happens to be showing. Question seven produces the identical reshuffle to question four, because there is only one reshuffle in the file.

What the arithmetic produces, once:

before after
1. The Brainwaves 3840 1. The Brainwaves 5180
2. Quiz Khalifa 3510 2. Quiz Khalifa 4690
3. Newton Johns 3200 3. Trivia Pursuit 3840
4. Trivia Pursuit 2890 4. The Know-It-Alls 3530
5. The Know-It-Alls 2650 5. Newton Johns 3200
6. Smarty Pints 2420 6. Smarty Pints 3140
7. Brain Freeze 2180 7. Quiz on My Face 2600
8. Quiz on My Face 1950 8. Brain Freeze 2180

Five of the eight rows move. The top two never do, which is deliberate: a demo where the leader changes reads as a different product claim than the one we make, and a demo where nothing moves shows nothing at all. Two tables score zero on the question, which is what pushes the middle around, and both of them fall. That is the whole dramatic content of the animation and it is eight lines of fixture.

The same two constants drive the third mock, the player's own phone, including the "you moved up" moment. Nothing in any of the three is computed from anything a visitor does.

The counter that looks like events

Above the leaderboard, the host view shows how many tables have answered. It increments as the countdown runs, which is the single most convincing detail in the whole section.

const TABLE_ANSWER_TIMES = [2, 4, 6, 8, 10, 13, 16, 19]

const answered = hostPhase === "results"
  ? TOTAL_TABLES
  : TABLE_ANSWER_TIMES.filter(s => s <= elapsed).length
Enter fullscreen mode Exit fullscreen mode

Eight tables, eight fixed moments, and the counter is a filter over the clock rather than a queue of anything. The gaps widen towards the end (two, then three) because real rooms do that, and the last table answers at 19 of 20 so the question looks like it is going to time out and then does not. Somebody thought about this for longer than the code suggests.

The part I would change

const myEntry = POST_Q4_SCORES.find(e => e.name === MY_TABLE)!
Enter fullscreen mode Exit fullscreen mode

MY_TABLE is the string "Table 3, The Brainwaves", declared at the top of the file. POST_Q4_SCORES derives from INITIAL_SCORES, where that same string is typed again. Q4_POINTS is an object keyed by those strings, so it is typed a third time. The lookup is by string equality, and the non-null assertion says "this will be found".

It will be found right up until somebody renames a team in one of the three places and not the others, at which point myEntry is undefined, the ! has told TypeScript not to worry, and the landing page throws during render. A fixture is exactly where that mistake gets made, because fixtures are where you go to change a name to something funnier.

The fix is to key the fixture on an id and derive the display name, or to index the array and let the index be the source of truth. Either is five minutes. Neither has been done, which is why it is in this post.

One small thing that made me laugh

Three of the eight fake teams are real entries from our team name generator: Quiz Khalifa, Smarty Pints and Brain Freeze are all in that pool of 110. A fourth, Trivia Pursuit, is the pool's "Trivia Pursuits" with the s removed, which I assume was a typo and which now looks like a deliberate joke about a team that is only pursuing one thing.

Nobody wired those together. Somebody needed eight plausible pub quiz team names, and the person who needed them had recently written a hundred and ten of them.

Go and watch it

The three mocks are on pub-trivia.app, roughly one screen down. Count the countdown against a real clock and you will see the double speed; watch the leaderboard twice and you will see the same five rows move in the same order. Then look at the live leaderboard page, which is the feature the mock is standing in for and is the thing that actually recomputes.

Top comments (0)