Improved VisitsTable performance by memoizing visits lists

This commit is contained in:
Alejandro Celaya 2020-04-04 12:58:04 +02:00
parent 06b63d1af2
commit bd4255108d
4 changed files with 23 additions and 26 deletions

View file

@ -35,8 +35,8 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default'
<Card className={cardClasses} body> <Card className={cardClasses} body>
<h3 className={classNames('text-center mb-0', getTextClassForType(type))}> <h3 className={classNames('text-center mb-0', getTextClassForType(type))}>
{loading && <FontAwesomeIcon icon={preloader} spin />} {loading && <FontAwesomeIcon icon={preloader} spin />}
{loading && !children && <span className="ml-2">Loading...</span>} {loading && <span className="ml-2">{children || 'Loading...'}</span>}
{children} {!loading && children}
</h3> </h3>
</Card> </Card>
</div> </div>

View file

@ -1,4 +1,8 @@
const TEN_ROUNDING_NUMBER = 10; const TEN_ROUNDING_NUMBER = 10;
const { ceil } = Math; 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; export const roundTen = (number) => ceil(number / TEN_ROUNDING_NUMBER) * TEN_ROUNDING_NUMBER;

View file

@ -78,6 +78,7 @@ const ShortUrlVisits = (
render() { render() {
const { shortUrlVisits, shortUrlDetail } = this.props; const { shortUrlVisits, shortUrlDetail } = this.props;
const { visits, loading, loadingLarge, error } = shortUrlVisits; const { visits, loading, loadingLarge, error } = shortUrlVisits;
const showTableControls = !loading && visits.length > 0;
const renderVisitsContent = () => { const renderVisitsContent = () => {
if (loading) { if (loading) {
@ -166,7 +167,7 @@ const ShortUrlVisits = (
/> />
</div> </div>
<div className="col-lg-4 col-xl-6 mt-4 mt-lg-0"> <div className="col-lg-4 col-xl-6 mt-4 mt-lg-0">
{visits.length > 0 && ( {showTableControls && (
<Button <Button
outline outline
block={this.state.isMobileDevice} block={this.state.isMobileDevice}
@ -179,7 +180,7 @@ const ShortUrlVisits = (
</div> </div>
</section> </section>
{!loading && visits.length > 0 && ( {showTableControls && (
<Collapse <Collapse
isOpen={this.state.showTable} isOpen={this.state.showTable}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Moment from 'react-moment'; import Moment from 'react-moment';
import classNames from 'classnames'; import classNames from 'classnames';
@ -13,6 +13,7 @@ import SimplePaginator from '../common/SimplePaginator';
import SearchField from '../utils/SearchField'; import SearchField from '../utils/SearchField';
import { browserFromUserAgent, extractDomain, osFromUserAgent } from '../utils/helpers/visits'; import { browserFromUserAgent, extractDomain, osFromUserAgent } from '../utils/helpers/visits';
import { determineOrderDir } from '../utils/utils'; import { determineOrderDir } from '../utils/utils';
import { prettify } from '../utils/helpers/numbers';
import { visitType } from './reducers/shortUrlVisits'; import { visitType } from './reducers/shortUrlVisits';
import './VisitsTable.scss'; import './VisitsTable.scss';
@ -56,39 +57,30 @@ const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => ({
})); }));
const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = window.matchMedia }) => { const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = window.matchMedia }) => {
const allVisits = normalizeVisits(visits);
const headerCellsClass = classNames('visits-table__header-cell', { const headerCellsClass = classNames('visits-table__header-cell', {
'visits-table__sticky': isSticky, 'visits-table__sticky': isSticky,
}); });
const matchMobile = () => matchMedia('(max-width: 767px)').matches; const matchMobile = () => matchMedia('(max-width: 767px)').matches;
const [ selectedVisit, setSelectedVisit ] = useState(undefined); const [ selectedVisit, setSelectedVisit ] = useState(undefined);
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ searchTerm, setSearchTerm ] = useState(undefined); const [ searchTerm, setSearchTerm ] = useState(undefined);
const [ order, setOrder ] = useState({ field: undefined, dir: undefined }); const [ order, setOrder ] = useState({ field: undefined, dir: undefined });
const [ currentPage, setCurrentPageVisits ] = useState(calculateVisits(allVisits, page, searchTerm, order)); const allVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile()); const currentPage = useMemo(() => calculateVisits(allVisits, page, searchTerm, order), [ page, searchTerm, order ]);
const orderByColumn = (field) => () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) }); const orderByColumn = (field) => () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) });
const renderOrderIcon = (field) => { const renderOrderIcon = (field) => order.dir && order.field === field && (
if (!order.dir || order.field !== field) {
return null;
}
return (
<FontAwesomeIcon <FontAwesomeIcon
icon={order.dir === 'ASC' ? caretUpIcon : caretDownIcon} icon={order.dir === 'ASC' ? caretUpIcon : caretDownIcon}
className="visits-table__header-icon" className="visits-table__header-icon"
/> />
); );
};
useEffect(() => { useEffect(() => {
onVisitSelected && onVisitSelected(selectedVisit); onVisitSelected && onVisitSelected(selectedVisit);
}, [ selectedVisit ]); }, [ selectedVisit ]);
useEffect(() => {
setCurrentPageVisits(calculateVisits(allVisits, page, searchTerm, order));
}, [ page, searchTerm, order ]);
useEffect(() => { useEffect(() => {
const listener = () => setIsMobileDevice(matchMobile()); const listener = () => setIsMobileDevice(matchMobile());
@ -188,9 +180,9 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w
})} })}
> >
<div> <div>
Visits <b>{currentPage.start + 1}</b> to{' '} Visits <b>{prettify(currentPage.start + 1)}</b> to{' '}
<b>{min(currentPage.end, currentPage.total)}</b> of{' '} <b>{prettify(min(currentPage.end, currentPage.total))}</b> of{' '}
<b>{currentPage.total}</b> <b>{prettify(currentPage.total)}</b>
</div> </div>
</div> </div>
</div> </div>