cache shuffled game order in zustand store to prevent card flicker
the discover tab re-shuffled on every mount because useGames() returns a new array reference each time. now the shuffled ID order is stored in zustand, only recomputed when game count or seen count actually changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { useGames, usePlaylist, usePlaylistMutations } from "@/shared/db/hooks"
|
||||
import type { Game } from "@/shared/db/schema"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { seededShuffle, useDiscoverStore } from "../store"
|
||||
import { useDiscoverStore } from "../store"
|
||||
|
||||
export function useDiscover() {
|
||||
const { games: allGames } = useGames()
|
||||
const { games: wantToPlayGames, reload: reloadWtp } = usePlaylist("want-to-play")
|
||||
const { games: notIntGames, reload: reloadNi } = usePlaylist("not-interesting")
|
||||
const { addGame } = usePlaylistMutations()
|
||||
const { currentIndex, setCurrentIndex, seed, reset } = useDiscoverStore()
|
||||
const { currentIndex, setCurrentIndex, updateShuffledGames, reset } = useDiscoverStore()
|
||||
const [ready, setReady] = useState(false)
|
||||
const [localSeenIds, setLocalSeenIds] = useState<Set<string>>(new Set())
|
||||
|
||||
@@ -21,12 +21,8 @@ export function useDiscover() {
|
||||
}, [wantToPlayGames, notIntGames, localSeenIds])
|
||||
|
||||
const unseenGames = useMemo(
|
||||
() =>
|
||||
seededShuffle(
|
||||
allGames.filter((g) => !seenIds.has(g.id)),
|
||||
seed,
|
||||
),
|
||||
[allGames, seenIds, seed],
|
||||
() => updateShuffledGames(allGames, seenIds),
|
||||
[allGames, seenIds, updateShuffledGames],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,21 +1,58 @@
|
||||
import type { Game } from "@/shared/db/schema"
|
||||
import { create } from "zustand"
|
||||
|
||||
interface DiscoverState {
|
||||
currentIndex: number
|
||||
seed: number
|
||||
animatingDirection: "left" | "right" | null
|
||||
/** Cached shuffled game order (game IDs) */
|
||||
shuffledIds: string[]
|
||||
/** Fingerprint of the input used to produce shuffledIds */
|
||||
shuffleKey: string
|
||||
setCurrentIndex: (index: number) => void
|
||||
setAnimatingDirection: (dir: "left" | "right" | null) => void
|
||||
/** Update the shuffled order only when the underlying game list changes */
|
||||
updateShuffledGames: (games: Game[], seenIds: Set<string>) => Game[]
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const useDiscoverStore = create<DiscoverState>((set) => ({
|
||||
function buildShuffleKey(games: Game[], seenIds: Set<string>, seed: number): string {
|
||||
return `${games.length}:${seenIds.size}:${seed}`
|
||||
}
|
||||
|
||||
export const useDiscoverStore = create<DiscoverState>((set, get) => ({
|
||||
currentIndex: 0,
|
||||
seed: Math.random(),
|
||||
animatingDirection: null,
|
||||
shuffledIds: [],
|
||||
shuffleKey: "",
|
||||
setCurrentIndex: (currentIndex) => set({ currentIndex }),
|
||||
setAnimatingDirection: (animatingDirection) => set({ animatingDirection }),
|
||||
reset: () => set({ currentIndex: 0, seed: Math.random(), animatingDirection: null }),
|
||||
updateShuffledGames: (games, seenIds) => {
|
||||
const { seed, shuffleKey, shuffledIds } = get()
|
||||
const key = buildShuffleKey(games, seenIds, seed)
|
||||
if (key === shuffleKey && shuffledIds.length > 0) {
|
||||
// Reuse cached order — just resolve IDs back to game objects
|
||||
const byId = new Map(games.map((g) => [g.id, g]))
|
||||
return shuffledIds.flatMap((id) => {
|
||||
if (seenIds.has(id)) return []
|
||||
const g = byId.get(id)
|
||||
return g ? [g] : []
|
||||
})
|
||||
}
|
||||
const unseen = games.filter((g) => !seenIds.has(g.id))
|
||||
const shuffled = seededShuffle(unseen, seed)
|
||||
set({ shuffledIds: shuffled.map((g) => g.id), shuffleKey: key })
|
||||
return shuffled
|
||||
},
|
||||
reset: () =>
|
||||
set({
|
||||
currentIndex: 0,
|
||||
seed: Math.random(),
|
||||
animatingDirection: null,
|
||||
shuffledIds: [],
|
||||
shuffleKey: "",
|
||||
}),
|
||||
}))
|
||||
|
||||
/** Mulberry32 — fast seeded 32-bit PRNG */
|
||||
|
||||
Reference in New Issue
Block a user