refactor(BattleTable): extract resultMap logic and simplify table building

- Move `initResultMap` and `buildMatchGroupTable` outside the component to reduce closure dependencies.
- Update `ClubEventList` to remove obsolete page key storage logic.
- Add `MATCH_RESULT_MAP_KEY` constant to `utils/constants.ts`.
- Refactor `useMemo` and `useCallback` hooks to rely on the extracted functions.
- Ensure consistent dependency arrays in callbacks for reliable re-renders.
This commit is contained in:
kyuuseiryuu 2026-03-16 23:46:22 +09:00
parent ce5c523efb
commit f29974c111
3 changed files with 49 additions and 54 deletions

View File

@ -6,6 +6,7 @@ import styled from "styled-components";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { ClearOutlined, EditOutlined, ShareAltOutlined } from "@ant-design/icons"; import { ClearOutlined, EditOutlined, ShareAltOutlined } from "@ant-design/icons";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
import { MATCH_RESULT_MAP_KEY } from "../utils/constants";
const StyledContainer = styled(Flex)` const StyledContainer = styled(Flex)`
min-width: 300px; min-width: 300px;
@ -30,31 +31,16 @@ function getMatchKey(player1: BasePlayer, player2: BasePlayer) {
return `${player1.uid}-${player2.uid}`; return `${player1.uid}-${player2.uid}`;
} }
function BattleTable({ function initResultMap() {
players: list,
}: {
players: BasePlayer[];
}) {
const playerList = useMemo(() => list.length % 2 === 0 ? list : [...list, {
uid: '',
score: '',
name: '-',
}], [list]);
const resultMap = useMemo(() => {
const cache = localStorage.getItem('match-result-map'); const cache = localStorage.getItem('match-result-map');
const cacheEntries: [string, Result][] = cache ? Object.entries(JSON.parse(cache)) : []; const cacheEntries: [string, Result][] = cache ? Object.entries(JSON.parse(cache)) : [];
return new Map<string, { return new Map<string, {
winer: string, winer: string,
score: number, score: number,
}>(cacheEntries); }>(cacheEntries);
}, []);
useEffect(() => {
return () => {
const cache = JSON.stringify(Object.fromEntries(resultMap.entries()));
localStorage.setItem('match-result-map', cache);
} }
}, [resultMap]);
const buildMatchGroupTable = useCallback((list: (BasePlayer | null)[]) => { const buildMatchGroupTable = (list: (BasePlayer | null)[], resultMap: Map<string, Result>) => {
const roundNum = list.length - 1; const roundNum = list.length - 1;
const dataList = cloneDeep(list); const dataList = cloneDeep(list);
const roundTable = new Array(roundNum).fill(0).map((_, i) => { const roundTable = new Array(roundNum).fill(0).map((_, i) => {
@ -76,8 +62,22 @@ function BattleTable({
}); });
return cloneDeep([left, right]); return cloneDeep([left, right]);
}); });
const cache = JSON.stringify(Object.fromEntries(resultMap.entries()));
localStorage.setItem(MATCH_RESULT_MAP_KEY, cache);
return roundTable; return roundTable;
}, []); };
function BattleTable({
players: list,
}: {
players: BasePlayer[];
}) {
const playerList = useMemo(() => list.length % 2 === 0 ? list : [...list, {
uid: '',
score: '',
name: '-',
}], [list]);
const resultMap = useMemo(() => initResultMap(), []);
const [matchGroupTable, setMatchGroupTable] = useState<ReturnType<typeof buildMatchGroupTable>>([]); const [matchGroupTable, setMatchGroupTable] = useState<ReturnType<typeof buildMatchGroupTable>>([]);
const matchKeys = useMemo(() => { const matchKeys = useMemo(() => {
const keys: string[] = []; const keys: string[] = [];
@ -99,16 +99,16 @@ function BattleTable({
} else { } else {
resultMap.set(key, { winer: winer?.uid!, score }); resultMap.set(key, { winer: winer?.uid!, score });
} }
setMatchGroupTable(buildMatchGroupTable(playerList)); setMatchGroupTable(buildMatchGroupTable(playerList, resultMap));
}, []); }, [playerList, resultMap]);
const handleClear = useCallback((left?: (BasePlayer | null | undefined)[], right?: (BasePlayer | null | undefined)[]) => { const handleClear = useCallback((left?: (BasePlayer | null | undefined)[], right?: (BasePlayer | null | undefined)[]) => {
left?.forEach((l, i) => { left?.forEach((l, i) => {
const r = right?.[i]; const r = right?.[i];
const key = getMatchKey(l!, r!); const key = getMatchKey(l!, r!);
resultMap.delete(key); resultMap.delete(key);
}); });
setMatchGroupTable(buildMatchGroupTable(playerList)); setMatchGroupTable(buildMatchGroupTable(playerList, resultMap));
}, [matchGroupTable, playerList]); }, [playerList, resultMap]);
const handleClearAll = useCallback(() => { const handleClearAll = useCallback(() => {
matchGroupTable.forEach(([left, right]) => { matchGroupTable.forEach(([left, right]) => {
left?.forEach((l, i) => { left?.forEach((l, i) => {
@ -117,8 +117,8 @@ function BattleTable({
resultMap.delete(key); resultMap.delete(key);
}); });
}); });
setMatchGroupTable(buildMatchGroupTable(playerList)); setMatchGroupTable(buildMatchGroupTable(playerList, resultMap));
}, [matchGroupTable, playerList]); }, [matchGroupTable, playerList, resultMap]);
const scoreDiff = useMemo(() => { const scoreDiff = useMemo(() => {
const [left = [], right = []] = matchGroupTable?.[matchGroupTable.length - 1] ?? []; const [left = [], right = []] = matchGroupTable?.[matchGroupTable.length - 1] ?? [];
const newScoreMap = Object.fromEntries([...left, ...right].map(e => [e?.uid, e?.score])); const newScoreMap = Object.fromEntries([...left, ...right].map(e => [e?.uid, e?.score]));
@ -154,9 +154,9 @@ function BattleTable({
setImportModalVisible(false); setImportModalVisible(false);
}, []); }, []);
useEffect(() => { useEffect(() => {
const roundTable = buildMatchGroupTable(playerList); const roundTable = buildMatchGroupTable(playerList, resultMap);
setMatchGroupTable(roundTable); setMatchGroupTable(roundTable);
}, [playerList]); }, [playerList, resultMap]);
const [shareCode, setShareCode] = useState(''); const [shareCode, setShareCode] = useState('');
const handleImportCode = useCallback(async () => { const handleImportCode = useCallback(async () => {
const data: Record<string, Result> = await fetch(`/api/battle/${eventId}?code=${shareCode}`) const data: Record<string, Result> = await fetch(`/api/battle/${eventId}?code=${shareCode}`)
@ -166,7 +166,7 @@ function BattleTable({
const { winer = '', score = 0 } = v ?? {}; const { winer = '', score = 0 } = v ?? {};
resultMap.set(k, { winer, score }); resultMap.set(k, { winer, score });
}); });
setMatchGroupTable(buildMatchGroupTable(playerList)); setMatchGroupTable(buildMatchGroupTable(playerList, resultMap));
setImportModalVisible(false); setImportModalVisible(false);
}, [shareCode, playerList]); }, [shareCode, playerList]);
// console.debug('matchGroupTable', matchGroupTable, gameResultMap); // console.debug('matchGroupTable', matchGroupTable, gameResultMap);

View File

@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { EventCard } from "./EventCard"; import { EventCard } from "./EventCard";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { CalendarOutlined } from "@ant-design/icons"; import { CalendarOutlined } from "@ant-design/icons";
import { STORE_PAGE_LIST_KEY } from "../utils/constants";
interface Props { interface Props {
clubId: string; clubId: string;
@ -44,11 +43,6 @@ export const ClubEvenList = (props: Props) => {
}, [showFinishedEvents, requestEvents.data?.data]); }, [showFinishedEvents, requestEvents.data?.data]);
const onPageInit = useCallback(async () => { const onPageInit = useCallback(async () => {
const storePageKey = getStorageKey(props.clubId); const storePageKey = getStorageKey(props.clubId);
const keys: string[] = JSON.parse(localStorage.getItem(STORE_PAGE_LIST_KEY) ?? '[]');
if (!keys.includes(storePageKey)) {
keys.push(storePageKey);
localStorage.setItem(STORE_PAGE_LIST_KEY, JSON.stringify(keys));
}
const prePage = Number(sessionStorage.getItem(storePageKey)) || 1; const prePage = Number(sessionStorage.getItem(storePageKey)) || 1;
const data = await requestEvents.runAsync(props.clubId, prePage).then(res => res.data); const data = await requestEvents.runAsync(props.clubId, prePage).then(res => res.data);
setPage(prePage); setPage(prePage);

View File

@ -1,3 +1,4 @@
export const LOGTO_RESOURCE = 'https://tt.ksr.la'; export const LOGTO_RESOURCE = 'https://tt.ksr.la';
export const CLUB_SELECTOR_KEY = 'CLUB_SELECTOR'; export const CLUB_SELECTOR_KEY = 'CLUB_SELECTOR';
export const STORE_PAGE_LIST_KEY = 'events-page-keys'; export const STORE_PAGE_LIST_KEY = 'events-page-keys';
export const MATCH_RESULT_MAP_KEY = 'match-result-map';