From 971e0ded495945af026719f3e2b1362e1085388c Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 14 Mar 2026 12:54:48 +0100 Subject: [PATCH] Replace ref+tick workaround with proper state in useBestiary Store creature map in useState instead of useRef with a dummy tick counter. React now re-renders naturally when the map changes. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/hooks/use-bestiary.ts | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/web/src/hooks/use-bestiary.ts b/apps/web/src/hooks/use-bestiary.ts index b0904d0..1bdc074 100644 --- a/apps/web/src/hooks/use-bestiary.ts +++ b/apps/web/src/hooks/use-bestiary.ts @@ -3,7 +3,7 @@ import type { Creature, CreatureId, } from "@initiative/domain"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { normalizeBestiary, setSourceDisplayNames, @@ -33,8 +33,9 @@ interface BestiaryHook { export function useBestiary(): BestiaryHook { const [isLoaded, setIsLoaded] = useState(false); - const creatureMapRef = useRef>(new Map()); - const [, setTick] = useState(0); + const [creatureMap, setCreatureMap] = useState( + () => new Map(), + ); useEffect(() => { const index = loadBestiaryIndex(); @@ -44,8 +45,7 @@ export function useBestiary(): BestiaryHook { } bestiaryCache.loadAllCachedCreatures().then((map) => { - creatureMapRef.current = map; - setTick((t) => t + 1); + setCreatureMap(map); }); }, []); @@ -63,9 +63,12 @@ export function useBestiary(): BestiaryHook { })); }, []); - const getCreature = useCallback((id: CreatureId): Creature | undefined => { - return creatureMapRef.current.get(id); - }, []); + const getCreature = useCallback( + (id: CreatureId): Creature | undefined => { + return creatureMap.get(id); + }, + [creatureMap], + ); const isSourceCachedFn = useCallback( (sourceCode: string): Promise => { @@ -86,10 +89,13 @@ export function useBestiary(): BestiaryHook { const creatures = normalizeBestiary(json); const displayName = getSourceDisplayName(sourceCode); await bestiaryCache.cacheSource(sourceCode, displayName, creatures); - for (const c of creatures) { - creatureMapRef.current.set(c.id, c); - } - setTick((t) => t + 1); + setCreatureMap((prev) => { + const next = new Map(prev); + for (const c of creatures) { + next.set(c.id, c); + } + return next; + }); }, [], ); @@ -100,18 +106,20 @@ export function useBestiary(): BestiaryHook { const creatures = normalizeBestiary(jsonData as any); const displayName = getSourceDisplayName(sourceCode); await bestiaryCache.cacheSource(sourceCode, displayName, creatures); - for (const c of creatures) { - creatureMapRef.current.set(c.id, c); - } - setTick((t) => t + 1); + setCreatureMap((prev) => { + const next = new Map(prev); + for (const c of creatures) { + next.set(c.id, c); + } + return next; + }); }, [], ); const refreshCache = useCallback(async (): Promise => { const map = await bestiaryCache.loadAllCachedCreatures(); - creatureMapRef.current = map; - setTick((t) => t + 1); + setCreatureMap(map); }, []); return {