Skip to content

Leaderboard "Active" streak list contains a duplicate user (same user.id, different count) → UI duplicates rows and DOM grows on tab toggle #9984

@oshliaer

Description

@oshliaer

What's wrong

On /leaderboard, the Longest Visit Streak → Active card contains two entries for the same user (id = 679b590f31e842a9fc848267, name Lost) with different streak values — 158 and 151. Toggling Active ↔ Lifetime tabs causes additional <li> elements for this user to accumulate in the DOM — after 3 toggles the Active list already shows three Lost — 158 rows.

Steps to reproduce

  1. Open https://roadmap.sh/leaderboard
  2. The «Longest Visit Streak» card opens on the Active tab by default — note the two Lost rows (158 and 151)
  3. Click Lifetime → then Active → repeat 2–3 times
  4. Duplicates of Lost — 158 start appearing in the Active list, and similarly in Lifetime; both cards visibly grow

Evidence — API response

GET https://api.roadmap.sh/v1-list-leaderboard-statsstreaks.active:

[
  { "id": "6712dee6791f57dd60d309cd", "name": "IgorLutiy", "count": 379 },
  { "id": "64f6acfa5ce9f4ca589060c4", "name": "samuel",    "count": 280 },
  ...
  { "id": "679b590f31e842a9fc848267", "name": "Lost",      "count": 158 },
  { "id": "6761b41e8fe51199dac494b4", "name": "Brofy",     "count": 152 },
  { "id": "679b590f31e842a9fc848267", "name": "Lost",      "count": 151 }
]

The same id appears twice in the array with different count values.

Root cause

Backend: the leaderboard aggregation does not deduplicate entries by user_id — a single user can land in streaks.active more than once (likely two stored streak sessions for the same account being concatenated without GROUP BY user_id / DISTINCT).

Frontend (amplifies the bug): src/components/Leaderboard/LeaderboardPage.tsx:172 uses <li key={user.id}>. When the API returns duplicate ids, React hits a key collision within a single list. Reconciliation on tab toggle becomes undefined and stale DOM nodes are not unmounted, so each Active ↔ Lifetime toggle adds another row.

Suggested fixes

  1. Primary — backend: guarantee user_id uniqueness in every leaderboard array (active, lifetime, and friends). E.g. DISTINCT ON (user_id) ORDER BY count DESC (Postgres) or the equivalent aggregation step.
  2. Defense on the frontend — change the key in LeaderboardPage.tsx:172:
    -                key={user.id}
    +                key={\`\${user.id}-\${counter}\`}
    This removes the reconciliation UB even if the backend ever regresses to returning duplicates again.

Environment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions