1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-25 10:32:31 +01:00

Fix excessive re-rendering due to route changesd

This commit is contained in:
DaneEveritt 2022-06-20 13:19:40 -04:00
parent 7b0e2ce99d
commit 8bd518048e
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
7 changed files with 51 additions and 27 deletions

View File

@ -11,7 +11,7 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'>
const useActivityLogs = (filters?: ActivityLogFilters, config?: ConfigInterface<PaginatedResult<ActivityLog>, AxiosError>): responseInterface<PaginatedResult<ActivityLog>, AxiosError> => { const useActivityLogs = (filters?: ActivityLogFilters, config?: ConfigInterface<PaginatedResult<ActivityLog>, AxiosError>): responseInterface<PaginatedResult<ActivityLog>, AxiosError> => {
const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const uuid = ServerContext.useStoreState(state => state.server.data?.uuid);
const key = useUserSWRContentKey([ 'server', 'activity', JSON.stringify(useFilteredObject(filters || {})) ]); const key = useUserSWRContentKey([ 'server', 'activity', useFilteredObject(filters || {}) ]);
return useSWR<PaginatedResult<ActivityLog>>(key, async () => { return useSWR<PaginatedResult<ActivityLog>>(key, async () => {
const { data } = await http.get(`/api/client/servers/${uuid}/activity`, { const { data } = await http.get(`/api/client/servers/${uuid}/activity`, {

View File

@ -6,16 +6,15 @@ import FlashMessageRender from '@/components/FlashMessageRender';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import PaginationFooter from '@/components/elements/table/PaginationFooter'; import PaginationFooter from '@/components/elements/table/PaginationFooter';
import { DesktopComputerIcon, XCircleIcon } from '@heroicons/react/solid'; import { DesktopComputerIcon, XCircleIcon } from '@heroicons/react/solid';
import { useLocation } from 'react-router';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import { styles as btnStyles } from '@/components/elements/button/index'; import { styles as btnStyles } from '@/components/elements/button/index';
import classNames from 'classnames'; import classNames from 'classnames';
import ActivityLogEntry from '@/components/elements/activity/ActivityLogEntry'; import ActivityLogEntry from '@/components/elements/activity/ActivityLogEntry';
import Tooltip from '@/components/elements/tooltip/Tooltip'; import Tooltip from '@/components/elements/tooltip/Tooltip';
import useLocationHash from '@/plugins/useLocationHash';
export default () => { export default () => {
const location = useLocation(); const { hash } = useLocationHash();
const { clearAndAddHttpError } = useFlashKey('account'); const { clearAndAddHttpError } = useFlashKey('account');
const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } }); const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } });
const { data, isValidating, error } = useActivityLogs(filters, { const { data, isValidating, error } = useActivityLogs(filters, {
@ -24,10 +23,8 @@ export default () => {
}); });
useEffect(() => { useEffect(() => {
const parsed = new URLSearchParams(location.search); setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
}, [ hash ]);
setFilters(value => ({ ...value, filters: { ip: parsed.get('ip'), event: parsed.get('event') } }));
}, [ location.search ]);
useEffect(() => { useEffect(() => {
clearAndAddHttpError(error); clearAndAddHttpError(error);

View File

@ -4,13 +4,13 @@ import Tooltip from '@/components/elements/tooltip/Tooltip';
import Translate from '@/components/elements/Translate'; import Translate from '@/components/elements/Translate';
import { format, formatDistanceToNowStrict } from 'date-fns'; import { format, formatDistanceToNowStrict } from 'date-fns';
import { ActivityLog } from '@definitions/user'; import { ActivityLog } from '@definitions/user';
import { useLocation } from 'react-router';
import ActivityLogMetaButton from '@/components/elements/activity/ActivityLogMetaButton'; import ActivityLogMetaButton from '@/components/elements/activity/ActivityLogMetaButton';
import { TerminalIcon } from '@heroicons/react/solid'; import { TerminalIcon } from '@heroicons/react/solid';
import classNames from 'classnames'; import classNames from 'classnames';
import style from './style.module.css'; import style from './style.module.css';
import { isObject } from '@/helpers'; import { isObject } from '@/helpers';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import useLocationHash from '@/plugins/useLocationHash';
interface Props { interface Props {
activity: ActivityLog; activity: ActivityLog;
@ -33,16 +33,8 @@ const formatProperties = (properties: Record<string, unknown>): Record<string, u
}; };
export default ({ activity, children }: Props) => { export default ({ activity, children }: Props) => {
const location = useLocation(); const { pathTo } = useLocationHash();
const actor = activity.relationships.actor; const actor = activity.relationships.actor;
const queryTo = (params: Record<string, string>): string => {
const current = new URLSearchParams(location.search);
Object.keys(params).forEach(key => current.set(key, params[key]));
return current.toString();
};
const properties = formatProperties(activity.properties); const properties = formatProperties(activity.properties);
return ( return (
@ -60,7 +52,7 @@ export default ({ activity, children }: Props) => {
</Tooltip> </Tooltip>
<span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span> <span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span>
<Link <Link
to={`?${queryTo({ event: activity.event })}`} to={`#${pathTo({ event: activity.event })}`}
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'} className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
> >
{activity.event} {activity.event}
@ -79,7 +71,7 @@ export default ({ activity, children }: Props) => {
</p> </p>
<div className={'mt-1 flex items-center text-sm'}> <div className={'mt-1 flex items-center text-sm'}>
<Link <Link
to={`?${queryTo({ ip: activity.ip })}`} to={`#${pathTo({ ip: activity.ip })}`}
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'} className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
> >
{activity.ip} {activity.ip}

View File

@ -1,3 +1,2 @@
export { ButtonProps } from './types';
export { default as Button } from './Button'; export { default as Button } from './Button';
export { default as styles } from './style.module.css'; export { default as styles } from './style.module.css';

View File

@ -11,8 +11,10 @@ import { Link } from 'react-router-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { styles as btnStyles } from '@/components/elements/button/index'; import { styles as btnStyles } from '@/components/elements/button/index';
import { XCircleIcon } from '@heroicons/react/solid'; import { XCircleIcon } from '@heroicons/react/solid';
import useLocationHash from '@/plugins/useLocationHash';
export default () => { export default () => {
const { hash } = useLocationHash();
const { clearAndAddHttpError } = useFlashKey('server:activity'); const { clearAndAddHttpError } = useFlashKey('server:activity');
const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } }); const [ filters, setFilters ] = useState<ActivityLogFilters>({ page: 1, sorts: { timestamp: -1 } });
@ -22,10 +24,8 @@ export default () => {
}); });
useEffect(() => { useEffect(() => {
const parsed = new URLSearchParams(location.search); setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
}, [ hash ]);
setFilters(value => ({ ...value, filters: { ip: parsed.get('ip'), event: parsed.get('event') } }));
}, [ location.search ]);
useEffect(() => { useEffect(() => {
clearAndAddHttpError(error); clearAndAddHttpError(error);

View File

@ -0,0 +1,30 @@
import { useLocation } from 'react-router';
import { useMemo } from 'react';
export default () => {
const location = useLocation();
const getHashObject = (value: string): Record<string, string> =>
value
.substring(1)
.split('&')
.reduce((obj, str) => {
const [ key, value = '' ] = str.split('=');
return !str.trim() ? obj : { ...obj, [key]: value };
}, {});
const pathTo = (params: Record<string, string>): string => {
const current = getHashObject(location.hash);
for (const key in params) {
current[key] = params[key];
}
return Object.keys(current).map(key => `${key}=${current[key]}`).join('&');
};
const hash = useMemo((): Record<string, string> => getHashObject(location.hash), [ location.hash ]);
return { hash, pathTo };
};

View File

@ -1,8 +1,14 @@
import { useStoreState } from '@/state/hooks'; import { useStoreState } from '@/state/hooks';
import { useDeepCompareMemo } from '@/plugins/useDeepCompareMemo';
export default (context: string | string[]) => { // eslint-disable-next-line @typescript-eslint/ban-types
const key = Array.isArray(context) ? context.join(':') : context; export default (context: string | string[] | (string | number | null | {})[]) => {
const uuid = useStoreState(state => state.user.data?.uuid); const uuid = useStoreState(state => state.user.data?.uuid);
const key = useDeepCompareMemo((): string => {
return (Array.isArray(context) ? context : [ context ])
.map((value) => JSON.stringify(value))
.join(':');
}, [ context ]);
if (!key.trim().length) { if (!key.trim().length) {
throw new Error('Must provide a valid context key to "useUserSWRContextKey".'); throw new Error('Must provide a valid context key to "useUserSWRContextKey".');