From 9c6dac09c44e5b6ba9dab51a59b48bc6a6f2f2a7 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Thu, 28 Aug 2025 23:16:23 +0200 Subject: [PATCH] Validate and reset corrupted table filter states Investigating people complaining develop.opencast.org being broken, it seems like there was a change to the filters which cannot handle the previous data which might still be stored in browsers. Like here means, it completely crashes the interface and users just see a blank page. The error they get is: ``` Uncaught TypeError: t.find is not a function fk tableFilterSelectors.ts:23 D reselect.mjs:647 i reselect.mjs:584 P reselect.mjs:659 i reselect.mjs:584 u TableFilters.tsx:55 Redux L use-sync-external-store-with-selector.production.js:40 h use-sync-external-store-with-selector.production.js:63 React 2 useSyncExternalStoreWithSelector use-sync-external-store-with-selector.production.js:74 n Redux qc TableFilters.tsx:55 React 14 K scheduler.production.js:152 tableFilterSelectors.ts:23:32 ``` This patch introduces a general validation for filters, resetting them if they are corrupted in the sense that the data structure is not correct. This closes #1381 This closes #1395 --- src/App.tsx | 6 ++++- src/hooks/useTableFilterStateValidation.ts | 26 ++++++++++++++++++++++ src/slices/tableFilterSlice.ts | 18 ++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useTableFilterStateValidation.ts diff --git a/src/App.tsx b/src/App.tsx index 6a0582e815..81c18f7daa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,9 +16,13 @@ import About from "./components/About"; import { useAppDispatch } from "./store"; import { fetchOcVersion, fetchUserInfo } from "./slices/userInfoSlice"; import { subscribeToAuthEvents } from "./utils/broadcastSync"; +import { useTableFilterStateValidation } from "./hooks/useTableFilterStateValidation"; function App() { const dispatch = useAppDispatch(); + + useTableFilterStateValidation(); + useEffect(() => { // Load information about current user on mount dispatch(fetchUserInfo()); @@ -33,7 +37,7 @@ function App() { dispatch(fetchUserInfo()); }); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/src/hooks/useTableFilterStateValidation.ts b/src/hooks/useTableFilterStateValidation.ts new file mode 100644 index 0000000000..6b6a93cff1 --- /dev/null +++ b/src/hooks/useTableFilterStateValidation.ts @@ -0,0 +1,26 @@ +import { useEffect } from "react"; +import { useAppDispatch, useAppSelector } from "../store"; +import { resetCorruptedState } from "../slices/tableFilterSlice"; + +/** + * Custom hook to validate and fix corrupted table filter state in localStorage. + * This hook should be used in components that rely on table filter state to ensure + * the state is valid before using it. + */ +export const useTableFilterStateValidation = () => { + const dispatch = useAppDispatch(); + const tableFilters = useAppSelector(state => state.tableFilters); + + useEffect(() => { + // Check for corrupted state and dispatch reset action if needed + const hasCorruption = + !Array.isArray(tableFilters.data) || + !Array.isArray(tableFilters.textFilter) || + !Array.isArray(tableFilters.stats); + + if (hasCorruption) { + console.warn("Detected corrupted table filter state, resetting to defaults"); + dispatch(resetCorruptedState()); + } + }, [dispatch, tableFilters.data, tableFilters.textFilter, tableFilters.stats]); +}; diff --git a/src/slices/tableFilterSlice.ts b/src/slices/tableFilterSlice.ts index 7b4f367549..bb784b5314 100644 --- a/src/slices/tableFilterSlice.ts +++ b/src/slices/tableFilterSlice.ts @@ -132,7 +132,7 @@ export const fetchStats = createAppAsyncThunk("tableFilters/fetchStats", async ( }[], description: string, order: number, -} + } // fetch information about possible status an event can have const data = await axios.get("/admin-ng/resources/STATS.json"); const response = data.data; @@ -380,6 +380,21 @@ const tableFilterSlice = createSlice({ removeSecondFilter(state) { state.secondFilter = ""; }, + resetCorruptedState(state) { + // Reset corrupted localStorage state to initial values + if (!Array.isArray(state.data)) { + console.warn("Resetting corrupted tableFilters.data to empty array"); + state.data = []; + } + if (!Array.isArray(state.textFilter)) { + console.warn("Resetting corrupted tableFilters.textFilter to empty array"); + state.textFilter = []; + } + if (!Array.isArray(state.stats)) { + console.warn("Resetting corrupted tableFilters.stats to empty array"); + state.stats = []; + } + }, }, extraReducers: builder => { builder @@ -427,6 +442,7 @@ export const { removeSelectedFilter, editSecondFilter, removeSecondFilter, + resetCorruptedState, } = tableFilterSlice.actions; // Export the slice reducer as the default export