From 372cf8c7e9b9786f90ea4dbc344b20e9c022737c Mon Sep 17 00:00:00 2001 From: Amin <26092352+aminamos@users.noreply.github.com> Date: Sun, 13 Sep 2026 19:54:26 -0500 Subject: [PATCH] feat: enable react hook rules part 2 (#164) --- eslint.config.mjs | 6 ++--- .../GbfsValidator/ValidationReport.tsx | 27 +++++++------------ .../screens/GbfsValidator/ValidationState.tsx | 20 ++++++-------- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index aa94178c..ff88d33a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -43,12 +43,12 @@ export default tseslint.config( ], // TODO: hooks called inside render callbacks, incorrect hook usage patterns, // and missing/extra effect dependencies — to be fixed in a separate ticket. - 'react-hooks/rules-of-hooks': 'off', + 'react-hooks/rules-of-hooks': 'error', 'react-hooks/refs': 'off', 'react-hooks/set-state-in-render': 'off', 'react-hooks/set-state-in-effect': 'off', - 'react-hooks/immutability': 'off', - 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/immutability': 'warn', + 'react-hooks/exhaustive-deps': 'warn', // TypeScript handles these; disable the core JS versions. 'no-undef': 'off', 'no-unused-vars': 'off', diff --git a/src/app/screens/GbfsValidator/ValidationReport.tsx b/src/app/screens/GbfsValidator/ValidationReport.tsx index 087f4150..625c1bf2 100644 --- a/src/app/screens/GbfsValidator/ValidationReport.tsx +++ b/src/app/screens/GbfsValidator/ValidationReport.tsx @@ -25,7 +25,7 @@ import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import LanguageIcon from '@mui/icons-material/Language'; import { type components } from '../../services/feeds/gbfs-validator-types'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { OpenInNew } from '@mui/icons-material'; import { ValidationReportTableStyles, @@ -45,6 +45,9 @@ export type ValidationResult = components['schemas']['ValidationResult']; export type GbfsFile = components['schemas']['GbfsFile']; export type FileError = components['schemas']['FileError']; +// needed as a module level constant to avoid re-rendering the component when the array is empty +const EMPTY_FILES: GbfsFile[] = []; + interface ValidationResultProps { validationResult: ValidationResult | undefined; loading: boolean; @@ -61,11 +64,9 @@ export default function ValidationReport({ const [groupedExpanded, setGroupedExpanded] = useState< Record >({}); - const allFiles: GbfsFile[] = validationResult?.summary?.files ?? []; - const baseFiles: GbfsFile[] = allFiles.filter((f) => f.language == null); - const languageSpecificFiles: GbfsFile[] = allFiles.filter( - (f) => f.language != null, - ); + const allFiles = validationResult?.summary?.files ?? EMPTY_FILES; + const baseFiles = allFiles.filter((f) => f.language == null); + const languageSpecificFiles = allFiles.filter((f) => f.language != null); const languages = Array.from( new Set(languageSpecificFiles.map((f) => f.language ?? '')), ).sort(); @@ -73,14 +74,7 @@ export default function ValidationReport({ languages[0] ?? '', ); - // Adjust selectedLanguage if languages set changes (e.g., after loading finishes) - useEffect(() => { - if (languages.length > 0 && !languages.includes(selectedLanguage)) { - setSelectedLanguage(languages[0]); - } - }, [languages, selectedLanguage]); - - const filesForLanguage: GbfsFile[] = + const filesForLanguage = selectedLanguage !== '' ? [ ...baseFiles, @@ -90,10 +84,7 @@ export default function ValidationReport({ ] : [...baseFiles]; // Group errors by fileName, normalized instancePath and message. Shared util ensures consistency. - const groupedByFile = useMemo( - () => groupErrorsByFile(filesForLanguage), - [filesForLanguage], - ); + const groupedByFile = groupErrorsByFile(filesForLanguage); const fileGroupRefs = useRef>([]); // Error details dialog selection state diff --git a/src/app/screens/GbfsValidator/ValidationState.tsx b/src/app/screens/GbfsValidator/ValidationState.tsx index 15129c4f..b6b5edf7 100644 --- a/src/app/screens/GbfsValidator/ValidationState.tsx +++ b/src/app/screens/GbfsValidator/ValidationState.tsx @@ -85,20 +85,14 @@ export default function ValidationState(): ReactElement { }; useEffect(() => { - let timer: ReturnType | null = null; - if (loadingState) { - timer = setTimeout(() => { - setLongLoadingState(true); - }, 5000); - } else { - setLongLoadingState(false); - } + if (!loadingState) return; + const timer = setTimeout(() => { + setLongLoadingState(true); + }, 5000); return () => { - // cleanup timer on unmount or when loadingState changes - if (timer != null) { - clearTimeout(timer); - } + clearTimeout(timer); + setLongLoadingState(false); }; }, [loadingState]); @@ -117,6 +111,7 @@ export default function ValidationState(): ReactElement { > @@ -250,6 +245,7 @@ export default function ValidationState(): ReactElement { */}