Skip to content

Commit 8367296

Browse files
committed
feat: added debounced storage
1 parent 639827c commit 8367296

6 files changed

Lines changed: 115 additions & 26 deletions

File tree

src/app/components/requestDetailsWrapper.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use client';
22

3-
import React, { Suspense } from 'react';
3+
import React, { Suspense, memo } from 'react';
44
import { ErrorBoundary } from 'react-error-boundary';
55
import DetailedRequest from '@/components/detailedRequest';
66
import { Data } from '@/lib/types/data';
7-
import { Protocol } from '@/lib/types/protocol';
87
import { View } from '@/lib/types/view';
98
import { IssuesListFallback, IssuesListErrorFallback } from '@/helpers/fallback-loaders';
109

@@ -13,7 +12,7 @@ interface RequestDetailsWrapperP {
1312
selectedInteractionData: Data;
1413
}
1514

16-
const RequestDetailsWrapper = (props: RequestDetailsWrapperP) => {
15+
const RequestDetailsWrapper = memo((props: RequestDetailsWrapperP) => {
1716
const { selectedInteractionData, view } = props;
1817

1918
return (
@@ -62,7 +61,7 @@ const RequestDetailsWrapper = (props: RequestDetailsWrapperP) => {
6261
</ErrorBoundary>
6362
</div>
6463
);
65-
};
64+
});
6665

6766
export default RequestDetailsWrapper;
6867

src/app/components/requestsTableWrapper.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { Suspense, useMemo } from 'react';
3+
import React, { Suspense, useMemo, memo } from 'react';
44
import { ErrorBoundary } from 'react-error-boundary';
55
import RequestsTable from '@/components/requestsTable';
66
import { Data } from '@/lib/types/data';
@@ -17,7 +17,7 @@ interface RequestsTableWrapperP {
1717
filter: Filter;
1818
}
1919

20-
const RequestsTableWrapper = ({
20+
const RequestsTableWrapper = memo(({
2121
data,
2222
handleRowClick,
2323
selectedInteraction,
@@ -43,7 +43,7 @@ const RequestsTableWrapper = ({
4343
</ErrorBoundary>
4444
</div>
4545
);
46-
};
46+
});
4747

4848
export default RequestsTableWrapper;
4949

src/app/page.tsx

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { StoredData } from '@/lib/types/storedData';
3131
import { Tab } from '@/lib/types/tab';
3232
import { View } from '@/lib/types/view';
3333
import { ThemeName, getTheme } from '@/theme';
34-
import { writeStoredData, getStoredData, defaultStoredData } from '@/lib/localStorage';
34+
import { writeStoredData, getStoredData, defaultStoredData, flushStoredData } from '@/lib/localStorage';
3535
import RequestDetailsWrapper from './components/requestDetailsWrapper';
3636
import RequestsTableWrapper from './components/requestsTableWrapper';
3737
import './styles.scss';
@@ -304,6 +304,40 @@ const HomePage = () => {
304304
}, [isClient]);
305305

306306
const pollingIntervalRef = useRef<number | undefined>(undefined);
307+
const isPageVisibleRef = useRef(true);
308+
309+
useEffect(() => {
310+
const handleVisibilityChange = () => {
311+
isPageVisibleRef.current = !document.hidden;
312+
313+
if (document.hidden) {
314+
if (pollingIntervalRef.current) {
315+
window.clearInterval(pollingIntervalRef.current);
316+
pollingIntervalRef.current = undefined;
317+
}
318+
flushStoredData();
319+
} else if (isClient && storedData.tabs.length > 0) {
320+
if (!pollingIntervalRef.current) {
321+
processPolledData(); // Immediate poll on return
322+
pollingIntervalRef.current = window.setInterval(() => {
323+
processPolledData();
324+
}, 4000);
325+
}
326+
}
327+
};
328+
329+
document.addEventListener('visibilitychange', handleVisibilityChange);
330+
331+
const handleBeforeUnload = () => {
332+
flushStoredData();
333+
};
334+
window.addEventListener('beforeunload', handleBeforeUnload);
335+
336+
return () => {
337+
document.removeEventListener('visibilitychange', handleVisibilityChange);
338+
window.removeEventListener('beforeunload', handleBeforeUnload);
339+
};
340+
}, [isClient, storedData.tabs.length, processPolledData]);
307341

308342
useEffect(() => {
309343
if (!isClient || storedData.tabs.length === 0) return;
@@ -312,9 +346,11 @@ const HomePage = () => {
312346
window.clearInterval(pollingIntervalRef.current);
313347
}
314348

315-
pollingIntervalRef.current = window.setInterval(() => {
316-
processPolledData();
317-
}, 4000);
349+
if (isPageVisibleRef.current) {
350+
pollingIntervalRef.current = window.setInterval(() => {
351+
processPolledData();
352+
}, 4000);
353+
}
318354

319355
const tempFilteredData = storedData.data
320356
.filter((item) => item['unique-id'] === storedData.selectedTab['unique-id']);
@@ -333,13 +369,15 @@ const HomePage = () => {
333369
item === storedData.selectedTab
334370
);
335371

372+
const theme = useMemo(() => getTheme(storedData.theme), [storedData.theme]);
373+
336374
if (!isClient) {
337375
return null;
338376
}
339377

340378
return (
341-
<ThemeProvider theme={getTheme(storedData.theme)}>
342-
<GlobalStyles theme={getTheme(storedData.theme)} />
379+
<ThemeProvider theme={theme}>
380+
<GlobalStyles theme={theme} />
343381
<div className="main">
344382
<AppLoader isRegistered={isRegistered} mode={loaderAnimationMode} />
345383
{aboutPopupVisibility && (

src/components/detailedRequest/index.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { useEffect, useRef } from 'react';
3+
import React, { useEffect, useRef, memo, useCallback } from 'react';
44
import Prism from 'prismjs';
55
import 'prismjs/themes/prism-dark.css';
66
import 'prismjs/components/prism-http';
@@ -17,13 +17,31 @@ interface DetailedRequestP {
1717
protocol: Protocol;
1818
}
1919

20-
const DetailedRequest = ({ title, data, view, protocol }: DetailedRequestP) => {
20+
const DetailedRequest = memo(({ title, data, view, protocol }: DetailedRequestP) => {
2121
const codeRef = useRef<HTMLElement>(null);
22+
const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
2223

2324
useEffect(() => {
24-
if (codeRef.current) {
25-
Prism.highlightElement(codeRef.current);
25+
// Debounce Prism highlighting to prevent blocking during rapid selection
26+
if (highlightTimeoutRef.current) {
27+
clearTimeout(highlightTimeoutRef.current);
2628
}
29+
30+
highlightTimeoutRef.current = setTimeout(() => {
31+
if (codeRef.current) {
32+
Prism.highlightElement(codeRef.current);
33+
}
34+
}, 100);
35+
36+
return () => {
37+
if (highlightTimeoutRef.current) {
38+
clearTimeout(highlightTimeoutRef.current);
39+
}
40+
};
41+
}, [data]);
42+
43+
const handleCopy = useCallback(() => {
44+
copyDataToClipboard(data);
2745
}, [data]);
2846

2947
return (
@@ -36,7 +54,7 @@ const DetailedRequest = ({ title, data, view, protocol }: DetailedRequestP) => {
3654
>
3755
<span>{title}</span>
3856
<div className="body">
39-
<button type="button" className="copy_button" onClick={() => copyDataToClipboard(data)}>
57+
<button type="button" className="copy_button" onClick={handleCopy}>
4058
Copy <CopyIcon />
4159
</button>
4260
<div className="pre_wrapper">
@@ -52,6 +70,6 @@ const DetailedRequest = ({ title, data, view, protocol }: DetailedRequestP) => {
5270
</div>
5371
</div>
5472
);
55-
};
73+
});
5674

5775
export default DetailedRequest;

src/components/requestsTable/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
3+
import React, { useEffect, useState, useCallback, useMemo, useRef, memo } from 'react';
44
import { formatDistance } from 'date-fns';
55
import { FilterIcon, FilterSelectedIcon } from '@/components/icons';
66
import { getStoredData, writeStoredData } from '@/lib/localStorage';
@@ -17,7 +17,7 @@ interface RequestsTableP {
1717
filter: Filter;
1818
}
1919

20-
const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: RequestsTableP) => {
20+
const RequestsTable = memo(({ data, handleRowClick, selectedInteraction, filter }: RequestsTableP) => {
2121
const [filterDropdownVisibility, setFilterDropdownVisibility] = useState<boolean>(false);
2222
const [filterValue, setFilterValue] = useState<Filter>(filter);
2323
const dropdownRef = useRef<HTMLDivElement>(null);
@@ -125,9 +125,9 @@ const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: Re
125125
</tbody>
126126
</table>
127127
);
128-
};
128+
});
129129

130-
const TableRow = React.memo(({
130+
const TableRow = memo(({
131131
item,
132132
index,
133133
total,

src/lib/localStorage/index.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,48 @@ const STORAGE_KEY = 'app';
44

55
export { defaultStoredData };
66

7+
// Debounce timer for localStorage writes
8+
let writeDebounceTimer: ReturnType<typeof setTimeout> | null = null;
9+
let pendingData: StoredData | null = null;
10+
11+
// Debounced write to prevent blocking main thread on rapid updates
712
export const writeStoredData = (data: StoredData): void => {
8-
if (typeof window !== 'undefined') {
13+
if (typeof window === 'undefined') return;
14+
15+
pendingData = data;
16+
17+
// Clear existing timer
18+
if (writeDebounceTimer) {
19+
clearTimeout(writeDebounceTimer);
20+
}
21+
22+
// Debounce writes by 300ms to batch rapid updates
23+
writeDebounceTimer = setTimeout(() => {
24+
if (pendingData) {
25+
try {
26+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pendingData));
27+
} catch (error) {
28+
console.error('Failed to write to localStorage:', error);
29+
}
30+
pendingData = null;
31+
}
32+
writeDebounceTimer = null;
33+
}, 300);
34+
};
35+
36+
// Force immediate write (for critical updates like before page unload)
37+
export const flushStoredData = (): void => {
38+
if (writeDebounceTimer) {
39+
clearTimeout(writeDebounceTimer);
40+
writeDebounceTimer = null;
41+
}
42+
if (pendingData && typeof window !== 'undefined') {
943
try {
10-
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
44+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pendingData));
1145
} catch (error) {
1246
console.error('Failed to write to localStorage:', error);
1347
}
48+
pendingData = null;
1449
}
1550
};
1651

@@ -23,7 +58,6 @@ export const getStoredData = (): StoredData => {
2358
const stored = localStorage.getItem(STORAGE_KEY);
2459
if (stored) {
2560
const parsed = JSON.parse(stored);
26-
// Merge with defaults to handle any missing fields
2761
return { ...defaultStoredData, ...parsed };
2862
}
2963
} catch (error) {

0 commit comments

Comments
 (0)