diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js
index 3010aef1..460106d2 100644
--- a/src/short-urls/ShortUrlsList.js
+++ b/src/short-urls/ShortUrlsList.js
@@ -1,7 +1,7 @@
import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { head, isEmpty, keys, values } from 'ramda';
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import qs from 'qs';
import PropTypes from 'prop-types';
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
@@ -20,148 +20,130 @@ export const SORTABLE_FIELDS = {
visits: 'Visits',
};
+const propTypes = {
+ listShortUrls: PropTypes.func,
+ resetShortUrlParams: PropTypes.func,
+ shortUrlsListParams: shortUrlsListParamsType,
+ match: PropTypes.object,
+ location: PropTypes.object,
+ loading: PropTypes.bool,
+ error: PropTypes.bool,
+ shortUrlsList: PropTypes.arrayOf(shortUrlType),
+ selectedServer: serverType,
+ createNewVisit: PropTypes.func,
+ mercureInfo: MercureInfoType,
+};
+
// FIXME Replace with typescript: (ShortUrlsRow component)
-const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Component {
- static propTypes = {
- listShortUrls: PropTypes.func,
- resetShortUrlParams: PropTypes.func,
- shortUrlsListParams: shortUrlsListParamsType,
- match: PropTypes.object,
- location: PropTypes.object,
- loading: PropTypes.bool,
- error: PropTypes.bool,
- shortUrlsList: PropTypes.arrayOf(shortUrlType),
- selectedServer: serverType,
- createNewVisit: PropTypes.func,
- mercureInfo: MercureInfoType,
- };
-
- refreshList = (extraParams) => {
- const { listShortUrls, shortUrlsListParams } = this.props;
-
- listShortUrls({
- ...shortUrlsListParams,
- ...extraParams,
+const ShortUrlsList = (ShortUrlsRow) => {
+ const ShortUrlsListComp = ({
+ listShortUrls,
+ resetShortUrlParams,
+ shortUrlsListParams,
+ match,
+ location,
+ loading,
+ error,
+ shortUrlsList,
+ selectedServer,
+ createNewVisit,
+ mercureInfo,
+ }) => {
+ const { orderBy } = shortUrlsListParams;
+ const [ order, setOrder ] = useState({
+ orderField: orderBy && head(keys(orderBy)),
+ orderDir: orderBy && head(values(orderBy)),
});
- };
-
- handleOrderBy = (orderField, orderDir) => {
- this.setState({ orderField, orderDir });
- this.refreshList({ orderBy: { [orderField]: orderDir } });
- };
-
- orderByColumn = (columnName) => () =>
- this.handleOrderBy(columnName, determineOrderDir(columnName, this.state.orderField, this.state.orderDir));
-
- renderOrderIcon = (field) => {
- if (this.state.orderField !== field) {
- return null;
- }
-
- if (!this.state.orderDir) {
- return null;
- }
-
- return (
-
- );
- };
-
- constructor(props) {
- super(props);
-
- const { orderBy } = props.shortUrlsListParams;
-
- this.state = {
- orderField: orderBy ? head(keys(orderBy)) : undefined,
- orderDir: orderBy ? head(values(orderBy)) : undefined,
+ const refreshList = (extraParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams });
+ const handleOrderBy = (orderField, orderDir) => {
+ setOrder({ orderField, orderDir });
+ refreshList({ orderBy: { [orderField]: orderDir } });
};
- }
+ const orderByColumn = (columnName) => () =>
+ handleOrderBy(columnName, determineOrderDir(columnName, order.orderField, order.orderDir));
+ const renderOrderIcon = (field) => {
+ if (order.orderField !== field) {
+ return null;
+ }
- componentDidMount() {
- const { match: { params }, location, shortUrlsListParams } = this.props;
- const query = qs.parse(location.search, { ignoreQueryPrefix: true });
- const tags = query.tag ? [ query.tag ] : shortUrlsListParams.tags;
+ if (!order.orderDir) {
+ return null;
+ }
- this.refreshList({ page: params.page, tags });
- }
-
- componentDidUpdate() {
- const { mercureHubUrl, token, loading, error } = this.props.mercureInfo;
-
- if (loading || error) {
- return;
- }
-
- const hubUrl = new URL(mercureHubUrl);
-
- hubUrl.searchParams.append('topic', 'https://shlink.io/new-visit');
- this.closeEventSource();
- this.es = new EventSource(hubUrl, {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
-
- this.es.onmessage = ({ data }) => this.props.createNewVisit(JSON.parse(data));
- }
-
- componentWillUnmount() {
- const { resetShortUrlParams } = this.props;
-
- this.closeEventSource();
- resetShortUrlParams();
- }
-
- closeEventSource = () => {
- if (this.es) {
- this.es.close();
- this.es = undefined;
- }
- }
-
- renderShortUrls() {
- const { shortUrlsList, selectedServer, loading, error, shortUrlsListParams } = this.props;
-
- if (error) {
return (
-
- Something went wrong while loading short URLs :( |
-
+
);
- }
+ };
+ const renderShortUrls = () => {
+ if (error) {
+ return (
+
+ Something went wrong while loading short URLs :( |
+
+ );
+ }
- if (loading) {
- return Loading... |
;
- }
+ if (loading) {
+ return Loading... |
;
+ }
- if (!loading && isEmpty(shortUrlsList)) {
- return No results found |
;
- }
+ if (!loading && isEmpty(shortUrlsList)) {
+ return No results found |
;
+ }
- return shortUrlsList.map((shortUrl) => (
-
- ));
- }
+ return shortUrlsList.map((shortUrl) => (
+
+ ));
+ };
+
+ useEffect(() => {
+ const { params } = match;
+ const query = qs.parse(location.search, { ignoreQueryPrefix: true });
+ const tags = query.tag ? [ query.tag ] : shortUrlsListParams.tags;
+
+ refreshList({ page: params.page, tags });
+
+ return resetShortUrlParams;
+ }, []);
+ useEffect(() => {
+ const { mercureHubUrl, token, loading, error } = mercureInfo;
+
+ if (loading || error) {
+ return undefined;
+ }
+
+ const hubUrl = new URL(mercureHubUrl);
+
+ hubUrl.searchParams.append('topic', 'https://shlink.io/new-visit');
+ const es = new EventSource(hubUrl, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ // es.onmessage = pipe(JSON.parse, createNewVisit);
+ es.onmessage = ({ data }) => createNewVisit(JSON.parse(data));
+
+ return () => es.close();
+ }, [ mercureInfo ]);
- render() {
return (
@@ -169,42 +151,46 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon
- {this.renderOrderIcon('dateCreated')}
+ {renderOrderIcon('dateCreated')}
Created at
|
- {this.renderOrderIcon('shortCode')}
+ {renderOrderIcon('shortCode')}
Short URL
|
- {this.renderOrderIcon('longUrl')}
+ {renderOrderIcon('longUrl')}
Long URL
|
Tags |
- {this.renderOrderIcon('visits')} Visits
+ {renderOrderIcon('visits')} Visits
|
|
- {this.renderShortUrls()}
+ {renderShortUrls()}
);
- }
+ };
+
+ ShortUrlsListComp.propTypes = propTypes;
+
+ return ShortUrlsListComp;
};
export default ShortUrlsList;
diff --git a/test/short-urls/ShortUrlsList.test.js b/test/short-urls/ShortUrlsList.test.js
index 95017966..050238b9 100644
--- a/test/short-urls/ShortUrlsList.test.js
+++ b/test/short-urls/ShortUrlsList.test.js
@@ -71,10 +71,6 @@ describe('', () => {
});
it('should render 6 table header cells with conditional order by icon', () => {
- const orderDirOptionToIconMap = {
- ASC: caretUpIcon,
- DESC: caretDownIcon,
- };
const getThElementForSortableField = (sortableField) => wrapper.find('table')
.find('thead')
.find('tr')
@@ -82,24 +78,18 @@ describe('', () => {
.filterWhere((e) => e.text().includes(SORTABLE_FIELDS[sortableField]));
Object.keys(SORTABLE_FIELDS).forEach((sortableField) => {
- const sortableThElementWrapper = getThElementForSortableField(sortableField);
+ expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(0);
- expect(sortableThElementWrapper.find(FontAwesomeIcon)).toHaveLength(0);
-
- sortableThElementWrapper.simulate('click');
+ getThElementForSortableField(sortableField).simulate('click');
expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(1);
- expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon).prop('icon')).toEqual(
- orderDirOptionToIconMap.ASC,
- );
+ expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon).prop('icon')).toEqual(caretUpIcon);
- sortableThElementWrapper.simulate('click');
+ getThElementForSortableField(sortableField).simulate('click');
expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(1);
- expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon).prop('icon')).toEqual(
- orderDirOptionToIconMap.DESC,
- );
+ expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon).prop('icon')).toEqual(caretDownIcon);
- sortableThElementWrapper.simulate('click');
- expect(sortableThElementWrapper.find(FontAwesomeIcon)).toHaveLength(0);
+ getThElementForSortableField(sortableField).simulate('click');
+ expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(0);
});
});
});