Converted ShortUrlVisits in functional component

This commit is contained in:
Alejandro Celaya 2020-04-07 22:33:41 +02:00
parent 8a486d991b
commit 310831a26a
2 changed files with 86 additions and 86 deletions

View file

@ -1,5 +1,5 @@
import { isEmpty, mapObjIndexed, values } from 'ramda';
import React from 'react';
import { isEmpty, values } from 'ramda';
import React, { useState, useEffect } from 'react';
import { Button, Card, Collapse } from 'reactstrap';
import PropTypes from 'prop-types';
import qs from 'qs';
@ -8,6 +8,7 @@ import { faChevronDown as chevronDown } from '@fortawesome/free-solid-svg-icons'
import DateRangeRow from '../utils/DateRangeRow';
import Message from '../utils/Message';
import { formatDate } from '../utils/helpers/date';
import { useToggle } from '../utils/helpers/hooks';
import SortableBarGraph from './SortableBarGraph';
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
import VisitsHeader from './VisitsHeader';
@ -15,71 +16,74 @@ import GraphCard from './GraphCard';
import { shortUrlDetailType } from './reducers/shortUrlDetail';
import VisitsTable from './VisitsTable';
const propTypes = {
match: PropTypes.shape({
params: PropTypes.object,
}),
location: PropTypes.shape({
search: PropTypes.string,
}),
getShortUrlVisits: PropTypes.func,
shortUrlVisits: shortUrlVisitsType,
getShortUrlDetail: PropTypes.func,
shortUrlDetail: shortUrlDetailType,
cancelGetShortUrlVisits: PropTypes.func,
matchMedia: PropTypes.func,
};
const highlightedVisitToStats = (highlightedVisit, prop) => highlightedVisit && { [highlightedVisit[prop]]: 1 };
const format = formatDate();
let memoizationId;
let timeWhenMounted;
const ShortUrlVisits = (
{ processStatsFromVisits },
OpenMapModalBtn
) => class ShortUrlVisits extends React.PureComponent {
static propTypes = {
match: PropTypes.shape({
params: PropTypes.object,
}),
location: PropTypes.shape({
search: PropTypes.string,
}),
getShortUrlVisits: PropTypes.func,
shortUrlVisits: shortUrlVisitsType,
getShortUrlDetail: PropTypes.func,
shortUrlDetail: shortUrlDetailType,
cancelGetShortUrlVisits: PropTypes.func,
matchMedia: PropTypes.func,
};
const ShortUrlVisits = ({ processStatsFromVisits }, OpenMapModalBtn) => {
const ShortUrlVisitsComp = ({
match,
location,
shortUrlVisits,
shortUrlDetail,
getShortUrlVisits,
getShortUrlDetail,
cancelGetShortUrlVisits,
matchMedia = window.matchMedia,
}) => {
const [ startDate, setStartDate ] = useState(undefined);
const [ endDate, setEndDate ] = useState(undefined);
const [ showTable, toggleTable ] = useToggle();
const [ tableIsSticky, , setSticky, unsetSticky ] = useToggle();
const [ highlightedVisit, setHighlightedVisit ] = useState(undefined);
const [ isMobileDevice, setIsMobileDevice ] = useState(false);
const determineIsMobileDevice = () => setIsMobileDevice(matchMedia('(max-width: 991px)').matches);
state = {
startDate: undefined,
endDate: undefined,
showTable: false,
tableIsSticky: false,
isMobileDevice: false,
highlightedVisit: undefined,
};
loadVisits = (loadDetail = false) => {
const { match: { params }, location: { search }, getShortUrlVisits, getShortUrlDetail } = this.props;
const { params } = match;
const { shortCode } = params;
const { startDate, endDate } = mapObjIndexed(formatDate(), this.state);
const { search } = location;
const { domain } = qs.parse(search, { ignoreQueryPrefix: true });
// While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calculations
this.memoizationId = `${this.timeWhenMounted}_${shortCode}_${startDate}_${endDate}`;
getShortUrlVisits(shortCode, { startDate, endDate, domain });
const loadVisits = () => {
const start = format(startDate);
const end = format(endDate);
if (loadDetail) {
// While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calculations
memoizationId = `${timeWhenMounted}_${shortCode}_${start}_${end}`;
getShortUrlVisits(shortCode, { startDate: start, endDate: end, domain });
};
useEffect(() => {
timeWhenMounted = new Date().getTime();
getShortUrlDetail(shortCode, domain);
}
};
determineIsMobileDevice();
window.addEventListener('resize', determineIsMobileDevice);
setIsMobileDevice = () => {
const { matchMedia = window.matchMedia } = this.props;
return () => {
cancelGetShortUrlVisits();
window.removeEventListener('resize', determineIsMobileDevice);
};
}, []);
useEffect(() => {
loadVisits();
}, [ startDate, endDate ]);
this.setState({ isMobileDevice: matchMedia('(max-width: 991px)').matches });
};
componentDidMount() {
this.timeWhenMounted = new Date().getTime();
this.loadVisits(true);
this.setIsMobileDevice();
window.addEventListener('resize', this.setIsMobileDevice);
}
componentWillUnmount() {
this.props.cancelGetShortUrlVisits();
window.removeEventListener('resize', this.setIsMobileDevice);
}
render() {
const { shortUrlVisits, shortUrlDetail } = this.props;
const { visits, loading, loadingLarge, error } = shortUrlVisits;
const showTableControls = !loading && visits.length > 0;
@ -103,7 +107,7 @@ const ShortUrlVisits = (
}
const { os, browsers, referrers, countries, cities, citiesForMap } = processStatsFromVisits(
{ id: this.memoizationId, visits }
{ id: memoizationId, visits }
);
const mapLocations = values(citiesForMap);
@ -120,7 +124,7 @@ const ShortUrlVisits = (
title="Referrers"
stats={referrers}
withPagination={false}
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'referer')}
highlightedStats={highlightedVisitToStats(highlightedVisit, 'referer')}
sortingItems={{
name: 'Referrer name',
amount: 'Visits amount',
@ -131,7 +135,7 @@ const ShortUrlVisits = (
<SortableBarGraph
title="Countries"
stats={countries}
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'country')}
highlightedStats={highlightedVisitToStats(highlightedVisit, 'country')}
sortingItems={{
name: 'Country name',
amount: 'Visits amount',
@ -142,10 +146,10 @@ const ShortUrlVisits = (
<SortableBarGraph
title="Cities"
stats={cities}
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'city')}
highlightedStats={highlightedVisitToStats(highlightedVisit, 'city')}
extraHeaderContent={(activeCities) =>
mapLocations.length > 0 &&
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
}
sortingItems={{
name: 'City name',
@ -156,7 +160,6 @@ const ShortUrlVisits = (
</div>
);
};
const setDate = (dateField) => (date) => this.setState({ [dateField]: date }, this.loadVisits);
return (
<React.Fragment>
@ -166,20 +169,21 @@ const ShortUrlVisits = (
<div className="row flex-md-row-reverse">
<div className="col-lg-8 col-xl-6">
<DateRangeRow
startDate={this.state.startDate}
endDate={this.state.endDate}
onStartDateChange={setDate('startDate')}
onEndDateChange={setDate('endDate')}
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
</div>
<div className="col-lg-4 col-xl-6 mt-4 mt-lg-0">
{showTableControls && (
<Button
outline
block={this.state.isMobileDevice}
onClick={() => this.setState(({ showTable }) => ({ showTable: !showTable }))}
block={isMobileDevice}
onClick={toggleTable}
>
Show table <FontAwesomeIcon icon={chevronDown} rotation={this.state.showTable ? 180 : undefined} />
{showTable ? 'Hide' : 'Show'} table{' '}
<FontAwesomeIcon icon={chevronDown} rotation={showTable ? 180 : undefined} />
</Button>
)}
</div>
@ -188,17 +192,13 @@ const ShortUrlVisits = (
{showTableControls && (
<Collapse
isOpen={this.state.showTable}
isOpen={showTable}
// Enable stickiness only when there's no CSS animation, to avoid weird rendering effects
onEntered={() => this.setState({ tableIsSticky: true })}
onExiting={() => this.setState({ tableIsSticky: false })}
onEntered={setSticky}
onExiting={unsetSticky}
>
<VisitsTable
visits={visits}
isSticky={this.state.tableIsSticky}
onVisitSelected={(highlightedVisit) => this.setState({ highlightedVisit })}
/>
<VisitsTable visits={visits} isSticky={tableIsSticky} onVisitSelected={setHighlightedVisit} />
</Collapse>
)}
@ -207,7 +207,11 @@ const ShortUrlVisits = (
</section>
</React.Fragment>
);
}
};
ShortUrlVisitsComp.propTypes = propTypes;
return ShortUrlVisitsComp;
};
export default ShortUrlVisits;

View file

@ -38,10 +38,7 @@ describe('<ShortUrlVisits />', () => {
return wrapper;
};
afterEach(() => {
getShortUrlVisitsMock.mockReset();
wrapper && wrapper.unmount();
});
afterEach(() => wrapper && wrapper.unmount());
it('renders a preloader when visits are loading', () => {
const wrapper = createComponent({ loading: true, visits: [] });
@ -91,9 +88,8 @@ describe('<ShortUrlVisits />', () => {
dateRange.simulate('endDateChange', '2016-01-02T00:00:00+01:00');
dateRange.simulate('endDateChange', '2016-01-03T00:00:00+01:00');
expect(getShortUrlVisitsMock).toHaveBeenCalledTimes(4);
expect(wrapper.state('startDate')).toEqual('2016-01-01T00:00:00+01:00');
expect(wrapper.state('endDate')).toEqual('2016-01-03T00:00:00+01:00');
expect(wrapper.find(DateRangeRow).prop('startDate')).toEqual('2016-01-01T00:00:00+01:00');
expect(wrapper.find(DateRangeRow).prop('endDate')).toEqual('2016-01-03T00:00:00+01:00');
});
it('holds the map button content generator on cities graph extraHeaderContent', () => {