From a79ae7e0c4459496da07ad2aa1bbdbb17a617372 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:11:52 +0100 Subject: [PATCH] fix: cuesheet header (#600) * `useLocalStorage` hook uses `useSyncExternalStore` --------- Co-authored-by: asharonbaltazar --- .../src/common/hooks/useLocalStorage.ts | 75 +++++++++---------- .../client/src/features/cuesheet/Cuesheet.tsx | 13 ++-- .../CuesheetHeader.tsx | 4 +- .../CuesheetTableSettings.tsx | 14 ++-- 4 files changed, 50 insertions(+), 56 deletions(-) diff --git a/apps/client/src/common/hooks/useLocalStorage.ts b/apps/client/src/common/hooks/useLocalStorage.ts index a6468e7af..3519821b9 100644 --- a/apps/client/src/common/hooks/useLocalStorage.ts +++ b/apps/client/src/common/hooks/useLocalStorage.ts @@ -1,53 +1,46 @@ -import { useEffect, useState } from 'react'; +import { useSyncExternalStore } from 'react'; -/** - * @description utility hook to handle state in local storage - * @param key - * @param initialValue - */ -export const useLocalStorage = (key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] => { - const [storedValue, setStoredValue] = useState(() => { - try { - const item = window.localStorage.getItem(`ontime-${key}`); - return item ? JSON.parse(item) : initialValue; - } catch (error) { - return initialValue; - } - }); +const STORAGE_EVENT = 'ontime-storage'; - useEffect(() => { - const handleStorageChange = (event: StorageEvent) => { - if (event.storageArea === window.localStorage && event.key === key) { - try { - const newValue = event.newValue ? JSON.parse(event.newValue) : initialValue; - setStoredValue(newValue); - } catch (_) { - /* empty */ - } - } - }; +function getSnapshot(key: string): string | null { + try { + return window.localStorage.getItem(`ontime-${key}`); + } catch { + return null; + } +} - window.addEventListener('storage', handleStorageChange); +function getParsedJson(localStorageValue: string | null, initialValue: T): T { + try { + return localStorageValue ? JSON.parse(localStorageValue) : initialValue; + } catch { + return initialValue; + } +} - return () => { - window.removeEventListener('storage', handleStorageChange); - }; - }, [initialValue, key]); +export const useLocalStorage = (key: string, initialValue: T) => { + const localStorageValue = useSyncExternalStore(subscribe, () => getSnapshot(key)); + const parsedLocalStorageValue = getParsedJson(localStorageValue, initialValue); /** * @description Set value to local storage * @param value */ - const setValue = (value: T | ((val: T) => T)) => { - try { - // Allow value to be a function so we have same API as useState - const valueToStore = value instanceof Function ? value(storedValue) : value; + const setLocalStorageValue = (value: T | ((val: T) => T)) => { + // Allow value to be a function so we have same API as useState + const valueToStore = value instanceof Function ? value(parsedLocalStorageValue) : value; - setStoredValue(valueToStore); - window.localStorage.setItem(`ontime-${key}`, JSON.stringify(valueToStore)); - } catch (error) { - console.error(error); - } + localStorage.setItem(`ontime-${key}`, JSON.stringify(valueToStore)); + window.dispatchEvent(new StorageEvent(STORAGE_EVENT)); }; - return [storedValue, setValue]; + + return [parsedLocalStorageValue, setLocalStorageValue] as const; }; + +function subscribe(callback: () => void) { + window.addEventListener(STORAGE_EVENT, callback); + + return () => { + window.removeEventListener(STORAGE_EVENT, callback); + }; +} diff --git a/apps/client/src/features/cuesheet/Cuesheet.tsx b/apps/client/src/features/cuesheet/Cuesheet.tsx index a674ec79d..e9e00ed6a 100644 --- a/apps/client/src/features/cuesheet/Cuesheet.tsx +++ b/apps/client/src/features/cuesheet/Cuesheet.tsx @@ -24,10 +24,7 @@ interface CuesheetProps { } export default function Cuesheet({ data, columns, handleUpdate, selectedId }: CuesheetProps) { - const followSelected = useCuesheetSettings((state) => state.followSelected); - const showSettings = useCuesheetSettings((state) => state.showSettings); - const showDelayBlock = useCuesheetSettings((state) => state.showDelayBlock); - const showPrevious = useCuesheetSettings((state) => state.showPrevious); + const { followSelected, showSettings, showDelayBlock, showPrevious } = useCuesheetSettings(); const [columnVisibility, setColumnVisibility] = useLocalStorage('table-hidden', {}); const [columnOrder, saveColumnOrder] = useLocalStorage('table-order', initialColumnOrder); @@ -66,7 +63,9 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu setColumnSizing({}); }; - const headerGroups = table.getHeaderGroups; + const headerGroups = table.getHeaderGroups(); + const rowModel = table.getRowModel(); + const allLeafColumns = table.getAllLeafColumns(); let eventIndex = 0; let isPast = Boolean(selectedId); @@ -75,7 +74,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu <> {showSettings && ( - {table.getRowModel().rows.map((row) => { + {rowModel.rows.map((row) => { const key = row.original.id; const isSelected = selectedId === key; if (isSelected) { diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx index a340fc098..91d8e8248 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx @@ -23,7 +23,7 @@ import { SortableCell } from './SortableCell'; import style from '../Cuesheet.module.scss'; interface CuesheetHeaderProps { - headerGroups: () => HeaderGroup[]; + headerGroups: HeaderGroup[]; } function CuesheetHeader(props: CuesheetHeaderProps) { @@ -75,7 +75,7 @@ function CuesheetHeader(props: CuesheetHeaderProps) { return ( - {headerGroups().map((headerGroup) => { + {headerGroups.map((headerGroup) => { const key = headerGroup.id; return ( diff --git a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx index e292a681c..722d41a97 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx @@ -22,12 +22,14 @@ interface CuesheetTableSettingsProps { function CuesheetTableSettings(props: CuesheetTableSettingsProps) { const { columns, handleResetResizing, handleResetReordering, handleClearToggles } = props; - const showPrevious = useCuesheetSettings((state) => state.showPrevious); - const togglePreviousVisibility = useCuesheetSettings((state) => state.togglePreviousVisibility); - const showDelayBlock = useCuesheetSettings((state) => state.showDelayBlock); - const toggleDelayVisibility = useCuesheetSettings((state) => state.toggleDelayVisibility); - const showDelayedTimes = useCuesheetSettings((state) => state.showDelayedTimes); - const toggleDelayedTimes = useCuesheetSettings((state) => state.toggleDelayedTimes); + const { + showPrevious, + toggleDelayVisibility, + showDelayBlock, + showDelayedTimes, + toggleDelayedTimes, + togglePreviousVisibility, + } = useCuesheetSettings(); return (