Skip to content

Commit d322aa0

Browse files
committed
feat: improved rendering and storage
1 parent c4c405c commit d322aa0

4 files changed

Lines changed: 138 additions & 73 deletions

File tree

src/app/components/requestDetailsWrapper.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ interface RequestDetailsWrapperP {
1515

1616
const RequestDetailsWrapper = (props: RequestDetailsWrapperP) => {
1717
const { selectedInteractionData, view } = props;
18-
console.log(selectedInteractionData.protocol);
1918

2019
return (
2120
<div
Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { Suspense } from 'react';
3+
import React, { Suspense, useMemo } from 'react';
44
import { ErrorBoundary } from 'react-error-boundary';
55
import RequestsTable from '@/components/requestsTable';
66
import { Data } from '@/lib/types/data';
@@ -22,24 +22,28 @@ const RequestsTableWrapper = ({
2222
handleRowClick,
2323
selectedInteraction,
2424
filter,
25-
}: RequestsTableWrapperP) => (
26-
<div className="requests_table_container">
27-
<ErrorBoundary
28-
FallbackComponent={({ resetErrorBoundary }) => (
29-
<RepoSidebarListErrorFallback retry={resetErrorBoundary} />
30-
)}
31-
>
32-
<Suspense fallback={<RepoSidebarListFallback />}>
33-
<RequestsTable
34-
data={[...data].reverse()}
35-
handleRowClick={handleRowClick}
36-
selectedInteraction={selectedInteraction}
37-
filter={filter}
38-
/>
39-
</Suspense>
40-
</ErrorBoundary>
41-
</div>
42-
);
25+
}: RequestsTableWrapperP) => {
26+
const reversedData = useMemo(() => [...data].reverse(), [data]);
27+
28+
return (
29+
<div className="requests_table_container">
30+
<ErrorBoundary
31+
FallbackComponent={({ resetErrorBoundary }) => (
32+
<RepoSidebarListErrorFallback retry={resetErrorBoundary} />
33+
)}
34+
>
35+
<Suspense fallback={<RepoSidebarListFallback />}>
36+
<RequestsTable
37+
data={reversedData}
38+
handleRowClick={handleRowClick}
39+
selectedInteraction={selectedInteraction}
40+
filter={filter}
41+
/>
42+
</Suspense>
43+
</ErrorBoundary>
44+
</div>
45+
);
46+
};
4347

4448
export default RequestsTableWrapper;
4549

src/app/page.tsx

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

3-
import React, { useEffect, useState, useCallback } from 'react';
3+
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
44
import { format } from 'date-fns';
55
import { ThemeProvider } from 'styled-components';
66

@@ -23,7 +23,6 @@ import {
2323
processData,
2424
handleResponseExport,
2525
copyDataToClipboard,
26-
clearIntervals,
2726
register,
2827
} from '@/lib';
2928
import { notifyTelegram, notifySlack, notifyDiscord } from '@/lib/notify';
@@ -50,11 +49,15 @@ const HomePage = () => {
5049
const [storedData, setStoredData] = useState<StoredData>(defaultStoredData);
5150
const [isCustomHostDialogVisible, setIsCustomHostDialogVisible] = useState(false);
5251
const [isClient, setIsClient] = useState(false);
52+
const hasLoadedInitialData = useRef(false);
53+
const filteredDataRef = useRef<Array<Data>>([]);
5354

5455
// Initialize stored data on client side
5556
useEffect(() => {
5657
setIsClient(true);
57-
setStoredData(getStoredData());
58+
const loadedData = getStoredData();
59+
setStoredData(loadedData);
60+
hasLoadedInitialData.current = true;
5861
}, []);
5962

6063
const handleResetPopupDialogVisibility = useCallback(() => {
@@ -123,11 +126,13 @@ const HomePage = () => {
123126
});
124127
};
125128

126-
const handleRowClick = (id: string) => {
129+
const handleRowClick = useCallback((id: string) => {
127130
setSelectedInteraction(id);
128-
const reqDetails = filteredData && filteredData[filteredData.findIndex((item) => item.id === id)];
129-
setSelectedInteractionData(reqDetails);
130-
};
131+
const reqDetails = filteredDataRef.current.find((item) => item.id === id);
132+
if (reqDetails) {
133+
setSelectedInteractionData(reqDetails);
134+
}
135+
}, []);
131136

132137
const handleDeleteTab = (tab: Tab) => {
133138
const { tabs } = storedData;
@@ -179,6 +184,7 @@ const HomePage = () => {
179184
...storedData,
180185
data: tempData,
181186
});
187+
filteredDataRef.current = [];
182188
setFilteredData([]);
183189
};
184190

@@ -199,7 +205,8 @@ const HomePage = () => {
199205
);
200206

201207
setIsRegistered(true);
202-
if (pollData?.data?.length !== 0 && !pollData.error) {
208+
209+
if (pollData?.data?.length > 0 && !pollData.error) {
203210
if (pollData.aes_key) {
204211
decryptedAESKey = decryptAESKey(privateKey, pollData.aes_key);
205212
}
@@ -236,9 +243,9 @@ const HomePage = () => {
236243
});
237244

238245
const newData = combinedData
239-
.filter((item) => item['unique-id'] === dataFromLocalStorage.selectedTab['unique-id'])
240-
.map((item) => item);
241-
setFilteredData([...newData]);
246+
.filter((item) => item['unique-id'] === dataFromLocalStorage.selectedTab['unique-id']);
247+
filteredDataRef.current = newData;
248+
setFilteredData(newData);
242249
}
243250
} catch (error) {
244251
console.error(error);
@@ -248,7 +255,9 @@ const HomePage = () => {
248255
}, [handleResetPopupDialogVisibility, handleCustomHostDialogVisibility]);
249256

250257
useEffect(() => {
251-
writeStoredData(storedData);
258+
if (hasLoadedInitialData.current) {
259+
writeStoredData(storedData);
260+
}
252261
}, [storedData]);
253262

254263
useEffect(() => {
@@ -261,18 +270,16 @@ const HomePage = () => {
261270
window.addEventListener('storage', handleStorageChange);
262271
setIsRegistered(true);
263272

273+
let registrationIntervalId: number | undefined;
274+
264275
if (storedData.correlationId === '') {
265276
setLoaderAnimationMode('loading');
266277
setIsRegistered(false);
267-
setTimeout(() => {
278+
const timeoutId = setTimeout(() => {
268279
register(storedData.host, storedData.token, false, false)
269280
.then((data) => {
270281
setStoredData(data);
271-
const intervalId = window.setInterval(() => {
272-
processPolledData();
273-
}, 4000);
274282
setIsRegistered(true);
275-
return intervalId;
276283
})
277284
.catch(() => {
278285
localStorage.clear();
@@ -281,28 +288,44 @@ const HomePage = () => {
281288
setIsRegistered(false);
282289
});
283290
}, 1500);
291+
292+
return () => {
293+
clearTimeout(timeoutId);
294+
window.removeEventListener('storage', handleStorageChange);
295+
if (registrationIntervalId) {
296+
window.clearInterval(registrationIntervalId);
297+
}
298+
};
284299
}
285300

286301
return () => {
287302
window.removeEventListener('storage', handleStorageChange);
288303
};
289304
}, [isClient]);
290305

306+
const pollingIntervalRef = useRef<number | undefined>(undefined);
307+
291308
useEffect(() => {
292309
if (!isClient || storedData.tabs.length === 0) return;
293310

294-
clearIntervals();
295-
const intervalId = window.setInterval(() => {
311+
if (pollingIntervalRef.current) {
312+
window.clearInterval(pollingIntervalRef.current);
313+
}
314+
315+
pollingIntervalRef.current = window.setInterval(() => {
296316
processPolledData();
297317
}, 4000);
298318

299319
const tempFilteredData = storedData.data
300-
.filter((item) => item['unique-id'] === storedData.selectedTab['unique-id'])
301-
.map((item) => item);
320+
.filter((item) => item['unique-id'] === storedData.selectedTab['unique-id']);
321+
filteredDataRef.current = tempFilteredData;
302322
setFilteredData(tempFilteredData);
303323

304324
return () => {
305-
window.clearInterval(intervalId);
325+
if (pollingIntervalRef.current) {
326+
window.clearInterval(pollingIntervalRef.current);
327+
pollingIntervalRef.current = undefined;
328+
}
306329
};
307330
}, [storedData.selectedTab, isClient, processPolledData]);
308331

@@ -365,7 +388,7 @@ const HomePage = () => {
365388
<TabSwitcher
366389
handleTabButtonClick={handleTabButtonClick}
367390
selectedTab={storedData.selectedTab}
368-
data={[...storedData.tabs]}
391+
data={storedData.tabs}
369392
handleAddNewTab={handleAddNewTab}
370393
handleDeleteTab={handleDeleteTab}
371394
handleTabRename={handleTabRename}
@@ -391,7 +414,7 @@ const HomePage = () => {
391414
/>
392415
</div>
393416
<RequestsTableWrapper
394-
data={[...filteredData]}
417+
data={filteredData}
395418
selectedInteraction={selectedInteraction || ''}
396419
handleRowClick={handleRowClick}
397420
filter={storedData.filter}

src/components/requestsTable/index.tsx

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

3-
import React, { useEffect, useState } from 'react';
3+
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
44
import { formatDistance } from 'date-fns';
55
import { FilterIcon, FilterSelectedIcon } from '@/components/icons';
66
import { getStoredData, writeStoredData } from '@/lib/localStorage';
@@ -18,28 +18,39 @@ interface RequestsTableP {
1818
}
1919

2020
const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: RequestsTableP) => {
21-
const [filteredData, setFilteredData] = useState<Data[]>(data);
2221
const [filterDropdownVisibility, setFilterDropdownVisibility] = useState<boolean>(false);
2322
const [filterValue, setFilterValue] = useState<Filter>(filter);
23+
const dropdownRef = useRef<HTMLDivElement>(null);
2424

2525
const isFiltered = trueKeys(filterValue).length !== protocols.length;
2626

27-
const filterData = (f: Filter) => filterByProtocols(trueKeys(f))(data);
27+
const filteredData = useMemo(() => {
28+
const activeProtocols = trueKeys(filterValue);
29+
return filterByProtocols(activeProtocols)(data);
30+
}, [data, filterValue]);
2831

2932
useEffect(() => {
30-
setFilteredData(filterData(filterValue));
31-
}, [data]);
32-
33-
const handleFilterDropdownVisibility = () => {
34-
const dropdownElement = document.getElementById('filter_dropdown');
35-
setFilterDropdownVisibility(!filterDropdownVisibility);
36-
document.addEventListener('click', (e: MouseEvent) => {
37-
const isClickInsideElement = dropdownElement?.contains(e.target as Node);
38-
if (!isClickInsideElement) {
33+
if (!filterDropdownVisibility) return;
34+
35+
const handleClickOutside = (e: MouseEvent) => {
36+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
3937
setFilterDropdownVisibility(false);
4038
}
41-
});
42-
};
39+
};
40+
41+
const timeoutId = setTimeout(() => {
42+
document.addEventListener('click', handleClickOutside);
43+
}, 0);
44+
45+
return () => {
46+
clearTimeout(timeoutId);
47+
document.removeEventListener('click', handleClickOutside);
48+
};
49+
}, [filterDropdownVisibility]);
50+
51+
const handleFilterDropdownVisibility = useCallback(() => {
52+
setFilterDropdownVisibility(prev => !prev);
53+
}, []);
4354

4455
useEffect(() => {
4556
const handleStorageChange = () => {
@@ -57,8 +68,6 @@ const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: Re
5768

5869
setFilterValue(newFilterValue);
5970
writeStoredData({ ...getStoredData(), filter: newFilterValue });
60-
61-
setFilteredData(filterData(newFilterValue));
6271
};
6372

6473
return (
@@ -68,7 +77,7 @@ const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: Re
6877
<th>#</th>
6978
<th>TIME</th>
7079
<th>
71-
<div id="filter_dropdown">
80+
<div id="filter_dropdown" ref={dropdownRef}>
7281
<div
7382
className={isFiltered ? '__filtered' : ''}
7483
onClick={handleFilterDropdownVisibility}
@@ -104,23 +113,53 @@ const RequestsTable = ({ data, handleRowClick, selectedInteraction, filter }: Re
104113
</thead>
105114
<tbody>
106115
{filteredData.map((item, i) => (
107-
<tr
116+
<TableRow
108117
key={item.id}
109-
onClick={() => handleRowClick(item.id)}
110-
className={item.id === selectedInteraction ? 'selected_row' : ''}
111-
>
112-
<td>{filteredData.length - i}</td>
113-
<td>
114-
{formatDistance(new Date(item.timestamp), new Date(), {
115-
addSuffix: true,
116-
})}
117-
</td>
118-
<td>{item.protocol}</td>
119-
</tr>
118+
item={item}
119+
index={i}
120+
total={filteredData.length}
121+
isSelected={item.id === selectedInteraction}
122+
onClick={handleRowClick}
123+
/>
120124
))}
121125
</tbody>
122126
</table>
123127
);
124128
};
125129

130+
const TableRow = React.memo(({
131+
item,
132+
index,
133+
total,
134+
isSelected,
135+
onClick
136+
}: {
137+
item: Data;
138+
index: number;
139+
total: number;
140+
isSelected: boolean;
141+
onClick: (id: string) => void;
142+
}) => {
143+
const handleClick = useCallback(() => {
144+
onClick(item.id);
145+
}, [onClick, item.id]);
146+
147+
// Memoize the formatted time to prevent recalculation on every render
148+
const formattedTime = useMemo(() =>
149+
formatDistance(new Date(item.timestamp), new Date(), { addSuffix: true }),
150+
[item.timestamp]
151+
);
152+
153+
return (
154+
<tr
155+
onClick={handleClick}
156+
className={isSelected ? 'selected_row' : ''}
157+
>
158+
<td>{total - index}</td>
159+
<td>{formattedTime}</td>
160+
<td>{item.protocol}</td>
161+
</tr>
162+
);
163+
});
164+
126165
export default RequestsTable;

0 commit comments

Comments
 (0)