import React, { useMemo, useEffect, useCallback } from "react"; import { Box, Typography } from "@mui/material"; import { useSearchParams } from "react-router-dom"; import { TABLE_DEFAULTS } from "./constants/defaults"; import { useLeaderboard } from "./context/LeaderboardContext"; import { useLeaderboardProcessing } from "./hooks/useLeaderboardData"; import { useLeaderboardData } from "./hooks/useLeaderboardData"; import LeaderboardTable from "./components/Table/Table"; import SearchBar, { SearchBarSkeleton } from "./components/Filters/SearchBar"; import PerformanceMonitor from "./components/PerformanceMonitor"; const Leaderboard = () => { const { state, actions } = useLeaderboard(); const [searchParams, setSearchParams] = useSearchParams(); const { data, isLoading: dataLoading, error: dataError, } = useLeaderboardData(); const { table, filteredData, error: processingError, } = useLeaderboardProcessing(); // Memoize filtered data const memoizedFilteredData = useMemo(() => filteredData, [filteredData]); const memoizedTable = useMemo(() => table, [table]); // Memoize table options const hasTableOptionsChanges = useMemo(() => { return ( state.display.rowSize !== TABLE_DEFAULTS.ROW_SIZE || JSON.stringify(state.display.scoreDisplay) !== JSON.stringify(TABLE_DEFAULTS.SCORE_DISPLAY) || state.display.averageMode !== TABLE_DEFAULTS.AVERAGE_MODE || state.display.rankingMode !== TABLE_DEFAULTS.RANKING_MODE ); }, [state.display]); const hasColumnFilterChanges = useMemo(() => { return ( JSON.stringify([...state.display.visibleColumns].sort()) !== JSON.stringify([...TABLE_DEFAULTS.COLUMNS.DEFAULT_VISIBLE].sort()) ); }, [state.display.visibleColumns]); // Memoize callbacks const onToggleFilters = useCallback(() => { actions.toggleFiltersExpanded(); }, [actions]); const onColumnVisibilityChange = useCallback( (newVisibility) => { actions.setDisplayOption( "visibleColumns", Object.keys(newVisibility).filter((key) => newVisibility[key]) ); }, [actions] ); const onRowSizeChange = useCallback( (size) => { actions.setDisplayOption("rowSize", size); }, [actions] ); const onScoreDisplayChange = useCallback( (display) => { actions.setDisplayOption("scoreDisplay", display); }, [actions] ); const onAverageModeChange = useCallback( (mode) => { actions.setDisplayOption("averageMode", mode); }, [actions] ); const onRankingModeChange = useCallback( (mode) => { actions.setDisplayOption("rankingMode", mode); }, [actions] ); const onPrecisionsChange = useCallback( (precisions) => { actions.setFilter("precisions", precisions); }, [actions] ); const onTypesChange = useCallback( (types) => { actions.setFilter("types", types); }, [actions] ); const onParamsRangeChange = useCallback( (range) => { actions.setFilter("paramsRange", range); }, [actions] ); const onBooleanFiltersChange = useCallback( (filters) => { actions.setFilter("booleanFilters", filters); }, [actions] ); const onReset = useCallback(() => { actions.resetFilters(); }, [actions]); // Memoize loading states const loadingStates = useMemo(() => { const isInitialLoading = dataLoading || !data; const isProcessingData = !memoizedTable || !memoizedFilteredData; const isApplyingFilters = state.models.length > 0 && !memoizedFilteredData; const hasValidFilterCounts = state.countsReady && state.filterCounts && state.filterCounts.normal && state.filterCounts.officialOnly; return { isInitialLoading, isProcessingData, isApplyingFilters, showSearchSkeleton: isInitialLoading || !hasValidFilterCounts, showFiltersSkeleton: isInitialLoading || !hasValidFilterCounts, showTableSkeleton: isInitialLoading || isProcessingData || isApplyingFilters || !hasValidFilterCounts, }; }, [ dataLoading, data, memoizedTable, memoizedFilteredData, state.models.length, state.filterCounts, state.countsReady, ]); // Memoize child components const memoizedSearchBar = useMemo( () => ( ), [ onToggleFilters, state.filtersExpanded, loadingStates.showTableSkeleton, memoizedFilteredData, table, ] ); // No need to memoize LeaderboardTable as it handles its own sorting state const tableComponent = ( ); // Update context with loaded data useEffect(() => { if (data) { actions.setModels(data); } }, [data, actions]); // Log to understand loading state useEffect(() => { if (process.env.NODE_ENV === "development") { console.log("Loading state:", { dataLoading, hasData: !!data, hasTable: !!table, hasFilteredData: !!filteredData, filteredDataLength: filteredData?.length, stateModelsLength: state.models.length, hasFilters: Object.keys(state.filters).some((key) => { if (Array.isArray(state.filters[key])) { return state.filters[key].length > 0; } return !!state.filters[key]; }), }); } }, [ dataLoading, data, table, filteredData?.length, state.models.length, filteredData, state.filters, ]); // If an error occurred, display it if (dataError || processingError) { return ( {(dataError || processingError)?.message || "An error occurred while loading the data"} ); } return ( {loadingStates.showSearchSkeleton ? ( ) : ( memoizedSearchBar )} {tableComponent} ); }; export default Leaderboard;