mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Improved VisitsTable performance by memoizing visits lists
This commit is contained in:
parent
06b63d1af2
commit
bd4255108d
4 changed files with 23 additions and 26 deletions
|
@ -35,8 +35,8 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default'
|
|||
<Card className={cardClasses} body>
|
||||
<h3 className={classNames('text-center mb-0', getTextClassForType(type))}>
|
||||
{loading && <FontAwesomeIcon icon={preloader} spin />}
|
||||
{loading && !children && <span className="ml-2">Loading...</span>}
|
||||
{children}
|
||||
{loading && <span className="ml-2">{children || 'Loading...'}</span>}
|
||||
{!loading && children}
|
||||
</h3>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
const TEN_ROUNDING_NUMBER = 10;
|
||||
const { ceil } = Math;
|
||||
|
||||
const formatter = new Intl.NumberFormat('en-US');
|
||||
|
||||
export const prettify = (number) => formatter.format(number);
|
||||
|
||||
export const roundTen = (number) => ceil(number / TEN_ROUNDING_NUMBER) * TEN_ROUNDING_NUMBER;
|
||||
|
|
|
@ -78,6 +78,7 @@ const ShortUrlVisits = (
|
|||
render() {
|
||||
const { shortUrlVisits, shortUrlDetail } = this.props;
|
||||
const { visits, loading, loadingLarge, error } = shortUrlVisits;
|
||||
const showTableControls = !loading && visits.length > 0;
|
||||
|
||||
const renderVisitsContent = () => {
|
||||
if (loading) {
|
||||
|
@ -166,7 +167,7 @@ const ShortUrlVisits = (
|
|||
/>
|
||||
</div>
|
||||
<div className="col-lg-4 col-xl-6 mt-4 mt-lg-0">
|
||||
{visits.length > 0 && (
|
||||
{showTableControls && (
|
||||
<Button
|
||||
outline
|
||||
block={this.state.isMobileDevice}
|
||||
|
@ -179,7 +180,7 @@ const ShortUrlVisits = (
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{!loading && visits.length > 0 && (
|
||||
{showTableControls && (
|
||||
<Collapse
|
||||
isOpen={this.state.showTable}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Moment from 'react-moment';
|
||||
import classNames from 'classnames';
|
||||
|
@ -13,6 +13,7 @@ import SimplePaginator from '../common/SimplePaginator';
|
|||
import SearchField from '../utils/SearchField';
|
||||
import { browserFromUserAgent, extractDomain, osFromUserAgent } from '../utils/helpers/visits';
|
||||
import { determineOrderDir } from '../utils/utils';
|
||||
import { prettify } from '../utils/helpers/numbers';
|
||||
import { visitType } from './reducers/shortUrlVisits';
|
||||
import './VisitsTable.scss';
|
||||
|
||||
|
@ -56,39 +57,30 @@ const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => ({
|
|||
}));
|
||||
|
||||
const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = window.matchMedia }) => {
|
||||
const allVisits = normalizeVisits(visits);
|
||||
const headerCellsClass = classNames('visits-table__header-cell', {
|
||||
'visits-table__sticky': isSticky,
|
||||
});
|
||||
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
|
||||
|
||||
const [ selectedVisit, setSelectedVisit ] = useState(undefined);
|
||||
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
|
||||
const [ page, setPage ] = useState(1);
|
||||
const [ searchTerm, setSearchTerm ] = useState(undefined);
|
||||
const [ order, setOrder ] = useState({ field: undefined, dir: undefined });
|
||||
const [ currentPage, setCurrentPageVisits ] = useState(calculateVisits(allVisits, page, searchTerm, order));
|
||||
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
|
||||
const allVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
|
||||
const currentPage = useMemo(() => calculateVisits(allVisits, page, searchTerm, order), [ page, searchTerm, order ]);
|
||||
|
||||
const orderByColumn = (field) => () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) });
|
||||
const renderOrderIcon = (field) => {
|
||||
if (!order.dir || order.field !== field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={order.dir === 'ASC' ? caretUpIcon : caretDownIcon}
|
||||
className="visits-table__header-icon"
|
||||
/>
|
||||
);
|
||||
};
|
||||
const renderOrderIcon = (field) => order.dir && order.field === field && (
|
||||
<FontAwesomeIcon
|
||||
icon={order.dir === 'ASC' ? caretUpIcon : caretDownIcon}
|
||||
className="visits-table__header-icon"
|
||||
/>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onVisitSelected && onVisitSelected(selectedVisit);
|
||||
}, [ selectedVisit ]);
|
||||
useEffect(() => {
|
||||
setCurrentPageVisits(calculateVisits(allVisits, page, searchTerm, order));
|
||||
}, [ page, searchTerm, order ]);
|
||||
useEffect(() => {
|
||||
const listener = () => setIsMobileDevice(matchMobile());
|
||||
|
||||
|
@ -188,9 +180,9 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w
|
|||
})}
|
||||
>
|
||||
<div>
|
||||
Visits <b>{currentPage.start + 1}</b> to{' '}
|
||||
<b>{min(currentPage.end, currentPage.total)}</b> of{' '}
|
||||
<b>{currentPage.total}</b>
|
||||
Visits <b>{prettify(currentPage.start + 1)}</b> to{' '}
|
||||
<b>{prettify(min(currentPage.end, currentPage.total))}</b> of{' '}
|
||||
<b>{prettify(currentPage.total)}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue