# BanGUI — Task List This document breaks the entire BanGUI project into development stages, ordered so that each stage builds on the previous one. Every task is described in prose with enough detail for a developer to begin work. References point to the relevant documentation. Reference: `Docs/Refactoring.md` for full analysis of each issue. --- ## Open Issues ### ~~WorldMap hover highlight: fill color sometimes does not change~~ ✅ Done **Status:** Completed. **Summary:** Memoized Geography style objects via `useMemo` keyed by `geographies`, `countries`, `selectedCountry`, and threshold props. Stabilized `onMouseEnter`, `onMouseMove`, and `onMouseLeave` handlers with `useCallback` using `data-*` attributes to avoid per-Geography closures. This ensures `React.memo(Geography)` correctly skips re-renders when only tooltip position changes, eliminating the state-update race that caused hover fills to flash back to defaults. **Symptom:** On mouse-over, a country's stroke correctly becomes bold (`strokeWidth: 1`), but the background fill colour sometimes stays at the default value instead of switching to the hover colour. **Root cause — `React.memo` is defeated by new style objects on every render:** `Geography.js` (`react-simple-maps-main/src/components/Geography.js`) is wrapped in `memo()`. It keeps an internal `isFocused` state that toggles between `style.default` and `style.hover`. This works correctly in isolation. However, in `WorldMap.tsx` (`frontend/src/components/WorldMap.tsx`): 1. **`onMouseMove`** calls `setTooltip(...)` on every pixel of mouse movement, triggering a GeoLayer re-render. 2. Each GeoLayer render creates a **new `style` object literal** for every `` (lines ~199–222). Because `memo()` does a shallow reference comparison, it sees a "new" `style` prop every time and **re-renders all ~200 Geography components** on every mouse-move event. 3. During these rapid cascading re-renders, React's batched state updates can cause `Geography`'s internal `setFocus(true)` (fired in `handleMouseEnter`) and the parent's `setTooltip` to race. When the parent re-render is processed before `isFocused` is committed, Geography momentarily renders with `isFocused = false` and picks `style.default` — the fill stays at the default colour while the stroke (which changes less dramatically) appears correct. A secondary contributing factor: for countries with `count === 0` that are not selected, `style.hover.fill` equals `style.default.fill` (both resolve to `fillColor`), so there is intentionally no visible fill change on hover for those countries. This may overlap with the perceived bug if the user expects all countries to highlight. **Fix — memoize the style objects so `memo()` can skip re-renders:** In `WorldMap.tsx`, compute each Geography's style object with `useMemo` (or move it outside the `.map()` callback with stable references). Concretely: 1. **Create a `useMemo`-based style map** keyed by `geo.rsmKey` that only recomputes when `countries`, `selectedCountry`, or the threshold props change — not on every tooltip update. ```tsx const styleMap = useMemo(() => { const map: Record = {}; for (const geo of geographies as { rsmKey: string; id: string | number }[]) { const numericId = String(geo.id); const cc = ISO_NUMERIC_TO_ALPHA2[numericId] ?? null; const count = cc !== null ? (countries[cc] ?? 0) : 0; const isSelected = cc !== null && selectedCountry === cc; const fillColor = getBanCountColor(count, thresholdLow, thresholdMedium, thresholdHigh); map[geo.rsmKey] = { default: { fill: isSelected ? tokens.colorBrandBackground : fillColor, stroke: tokens.colorNeutralStroke2, strokeWidth: 0.75, outline: "none", }, hover: { fill: isSelected ? tokens.colorBrandBackgroundHover : cc && count > 0 ? tokens.colorNeutralBackground3 : fillColor, stroke: tokens.colorNeutralStroke1, strokeWidth: 1, outline: "none", }, pressed: { fill: cc ? tokens.colorBrandBackgroundPressed : fillColor, stroke: tokens.colorBrandStroke1, strokeWidth: 1, outline: "none", }, }; } return map; }, [geographies, countries, selectedCountry, thresholdLow, thresholdMedium, thresholdHigh]); ``` 2. **Pass the memoized style** in the `.map()` callback: ```tsx ``` 3. **Wrap event handlers in `useCallback`** (`onMouseEnter`, `onMouseMove`, `onMouseLeave`) or move them outside the `.map()` so they are stable references and do not defeat `memo()`. Consider passing `cc`, `count`, and `countryNames` as data attributes and reading them from `e.currentTarget` inside a single shared handler. With stable `style` references, `memo(Geography)` will correctly skip re-renders for countries whose data has not changed, eliminating the state-update race condition and the unnecessary rendering of ~200 SVG paths on every mouse-move event. **Files to change:** - `frontend/src/components/WorldMap.tsx` — memoize style objects and stabilize event handlers. - No changes needed in `react-simple-maps-main/src/components/Geography.js`.