Skip to content

Commit 8bd5180

Browse files
committed
Fix excessive re-rendering due to route changesd
1 parent 7b0e2ce commit 8bd5180

File tree

7 files changed

+51
-27
lines changed

7 files changed

+51
-27
lines changed

resources/scripts/api/server/activity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'>
1111

1212
const useActivityLogs = (filters?: ActivityLogFilters, config?: ConfigInterface<PaginatedResult<ActivityLog>, AxiosError>): responseInterface<PaginatedResult<ActivityLog>, AxiosError> => {
1313
const uuid = ServerContext.useStoreState(state => state.server.data?.uuid);
14-
const key = useUserSWRContentKey([ 'server', 'activity', JSON.stringify(useFilteredObject(filters || {})) ]);
14+
const key = useUserSWRContentKey([ 'server', 'activity', useFilteredObject(filters || {}) ]);
1515

1616
return useSWR<PaginatedResult<ActivityLog>>(key, async () => {
1717
const { data } = await http.get(`/api/client/servers/${uuid}/activity`, {

resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ import FlashMessageRender from '@/components/FlashMessageRender';
66
import { Link } from 'react-router-dom';
77
import PaginationFooter from '@/components/elements/table/PaginationFooter';
88
import { DesktopComputerIcon, XCircleIcon } from '@heroicons/react/solid';
9-
import { useLocation } from 'react-router';
109
import Spinner from '@/components/elements/Spinner';
1110
import { styles as btnStyles } from '@/components/elements/button/index';
1211
import classNames from 'classnames';
1312
import ActivityLogEntry from '@/components/elements/activity/ActivityLogEntry';
1413
import Tooltip from '@/components/elements/tooltip/Tooltip';
14+
import useLocationHash from '@/plugins/useLocationHash';
1515

1616
export default () => {
17-
const location = useLocation();
18-
17+
const { hash } = useLocationHash();
1918
const { clearAndAddHttpError } = useFlashKey('account');
2019
const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } });
2120
const { data, isValidating, error } = useActivityLogs(filters, {
@@ -24,10 +23,8 @@ export default () => {
2423
});
2524

2625
useEffect(() => {
27-
const parsed = new URLSearchParams(location.search);
28-
29-
setFilters(value => ({ ...value, filters: { ip: parsed.get('ip'), event: parsed.get('event') } }));
30-
}, [ location.search ]);
26+
setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
27+
}, [ hash ]);
3128

3229
useEffect(() => {
3330
clearAndAddHttpError(error);

resources/scripts/components/elements/activity/ActivityLogEntry.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import Tooltip from '@/components/elements/tooltip/Tooltip';
44
import Translate from '@/components/elements/Translate';
55
import { format, formatDistanceToNowStrict } from 'date-fns';
66
import { ActivityLog } from '@definitions/user';
7-
import { useLocation } from 'react-router';
87
import ActivityLogMetaButton from '@/components/elements/activity/ActivityLogMetaButton';
98
import { TerminalIcon } from '@heroicons/react/solid';
109
import classNames from 'classnames';
1110
import style from './style.module.css';
1211
import { isObject } from '@/helpers';
1312
import Avatar from '@/components/Avatar';
13+
import useLocationHash from '@/plugins/useLocationHash';
1414

1515
interface Props {
1616
activity: ActivityLog;
@@ -33,16 +33,8 @@ const formatProperties = (properties: Record<string, unknown>): Record<string, u
3333
};
3434

3535
export default ({ activity, children }: Props) => {
36-
const location = useLocation();
36+
const { pathTo } = useLocationHash();
3737
const actor = activity.relationships.actor;
38-
39-
const queryTo = (params: Record<string, string>): string => {
40-
const current = new URLSearchParams(location.search);
41-
Object.keys(params).forEach(key => current.set(key, params[key]));
42-
43-
return current.toString();
44-
};
45-
4638
const properties = formatProperties(activity.properties);
4739

4840
return (
@@ -60,7 +52,7 @@ export default ({ activity, children }: Props) => {
6052
</Tooltip>
6153
<span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span>
6254
<Link
63-
to={`?${queryTo({ event: activity.event })}`}
55+
to={`#${pathTo({ event: activity.event })}`}
6456
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
6557
>
6658
{activity.event}
@@ -79,7 +71,7 @@ export default ({ activity, children }: Props) => {
7971
</p>
8072
<div className={'mt-1 flex items-center text-sm'}>
8173
<Link
82-
to={`?${queryTo({ ip: activity.ip })}`}
74+
to={`#${pathTo({ ip: activity.ip })}`}
8375
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
8476
>
8577
{activity.ip}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export { ButtonProps } from './types';
21
export { default as Button } from './Button';
32
export { default as styles } from './style.module.css';

resources/scripts/components/server/ServerActivityLogContainer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { Link } from 'react-router-dom';
1111
import classNames from 'classnames';
1212
import { styles as btnStyles } from '@/components/elements/button/index';
1313
import { XCircleIcon } from '@heroicons/react/solid';
14+
import useLocationHash from '@/plugins/useLocationHash';
1415

1516
export default () => {
17+
const { hash } = useLocationHash();
1618
const { clearAndAddHttpError } = useFlashKey('server:activity');
1719
const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } });
1820

@@ -22,10 +24,8 @@ export default () => {
2224
});
2325

2426
useEffect(() => {
25-
const parsed = new URLSearchParams(location.search);
26-
27-
setFilters(value => ({ ...value, filters: { ip: parsed.get('ip'), event: parsed.get('event') } }));
28-
}, [ location.search ]);
27+
setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
28+
}, [ hash ]);
2929

3030
useEffect(() => {
3131
clearAndAddHttpError(error);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useLocation } from 'react-router';
2+
import { useMemo } from 'react';
3+
4+
export default () => {
5+
const location = useLocation();
6+
7+
const getHashObject = (value: string): Record<string, string> =>
8+
value
9+
.substring(1)
10+
.split('&')
11+
.reduce((obj, str) => {
12+
const [ key, value = '' ] = str.split('=');
13+
14+
return !str.trim() ? obj : { ...obj, [key]: value };
15+
}, {});
16+
17+
const pathTo = (params: Record<string, string>): string => {
18+
const current = getHashObject(location.hash);
19+
20+
for (const key in params) {
21+
current[key] = params[key];
22+
}
23+
24+
return Object.keys(current).map(key => `${key}=${current[key]}`).join('&');
25+
};
26+
27+
const hash = useMemo((): Record<string, string> => getHashObject(location.hash), [ location.hash ]);
28+
29+
return { hash, pathTo };
30+
};

resources/scripts/plugins/useUserSWRContentKey.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { useStoreState } from '@/state/hooks';
2+
import { useDeepCompareMemo } from '@/plugins/useDeepCompareMemo';
23

3-
export default (context: string | string[]) => {
4-
const key = Array.isArray(context) ? context.join(':') : context;
4+
// eslint-disable-next-line @typescript-eslint/ban-types
5+
export default (context: string | string[] | (string | number | null | {})[]) => {
56
const uuid = useStoreState(state => state.user.data?.uuid);
7+
const key = useDeepCompareMemo((): string => {
8+
return (Array.isArray(context) ? context : [ context ])
9+
.map((value) => JSON.stringify(value))
10+
.join(':');
11+
}, [ context ]);
612

713
if (!key.trim().length) {
814
throw new Error('Must provide a valid context key to "useUserSWRContextKey".');

0 commit comments

Comments
 (0)