Converted ShortUrlsRow component into a functional component

This commit is contained in:
Alejandro Celaya
2020-03-06 21:44:03 +01:00
parent 972eafab34
commit f55d3a66aa
5 changed files with 49 additions and 51 deletions

View File

@@ -13,40 +13,36 @@ import Tag from '../../tags/helpers/Tag';
import ShortUrlVisitsCount from './ShortUrlVisitsCount'; import ShortUrlVisitsCount from './ShortUrlVisitsCount';
import './ShortUrlsRow.scss'; import './ShortUrlsRow.scss';
const propTypes = {
refreshList: PropTypes.func,
shortUrlsListParams: shortUrlsListParamsType,
selectedServer: serverType,
shortUrl: shortUrlType,
};
const ShortUrlsRow = ( const ShortUrlsRow = (
ShortUrlsRowMenu, ShortUrlsRowMenu,
colorGenerator, colorGenerator,
stateFlagTimeout useStateFlagTimeout
) => class ShortUrlsRow extends React.Component { ) => {
static propTypes = { const ShortUrlsRowComp = ({ shortUrl, selectedServer, refreshList, shortUrlsListParams }) => {
refreshList: PropTypes.func, const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout(false);
shortUrlsListParams: shortUrlsListParamsType, const renderTags = (tags) => {
selectedServer: serverType, if (isEmpty(tags)) {
shortUrl: shortUrlType, return <i className="indivisible"><small>No tags</small></i>;
}; }
state = { copiedToClipboard: false }; const selectedTags = shortUrlsListParams.tags || [];
renderTags(tags) { return tags.map((tag) => (
if (isEmpty(tags)) { <Tag
return <i className="indivisible"><small>No tags</small></i>; colorGenerator={colorGenerator}
} key={tag}
text={tag}
const { refreshList, shortUrlsListParams } = this.props; onClick={() => refreshList({ tags: [ ...selectedTags, tag ] })}
const selectedTags = shortUrlsListParams.tags || []; />
));
return tags.map((tag) => ( };
<Tag
colorGenerator={colorGenerator}
key={tag}
text={tag}
onClick={() => refreshList({ tags: [ ...selectedTags, tag ] })}
/>
));
}
render() {
const { shortUrl, selectedServer } = this.props;
return ( return (
<tr className="short-urls-row"> <tr className="short-urls-row">
@@ -56,16 +52,10 @@ const ShortUrlsRow = (
<td className="short-urls-row__cell" data-th="Short URL: "> <td className="short-urls-row__cell" data-th="Short URL: ">
<span className="indivisible short-urls-row__cell--relative"> <span className="indivisible short-urls-row__cell--relative">
<ExternalLink href={shortUrl.shortUrl} /> <ExternalLink href={shortUrl.shortUrl} />
<CopyToClipboard <CopyToClipboard text={shortUrl.shortUrl} onCopy={setCopiedToClipboard}>
text={shortUrl.shortUrl}
onCopy={() => stateFlagTimeout(this.setState.bind(this), 'copiedToClipboard')}
>
<FontAwesomeIcon icon={copyIcon} className="ml-2 short-urls-row__copy-btn" /> <FontAwesomeIcon icon={copyIcon} className="ml-2 short-urls-row__copy-btn" />
</CopyToClipboard> </CopyToClipboard>
<span <span className="badge badge-warning short-urls-row__copy-hint" hidden={!copiedToClipboard}>
className="badge badge-warning short-urls-row__copy-hint"
hidden={!this.state.copiedToClipboard}
>
Copied short URL! Copied short URL!
</span> </span>
</span> </span>
@@ -73,7 +63,7 @@ const ShortUrlsRow = (
<td className="short-urls-row__cell short-urls-row__cell--break" data-th="Long URL: "> <td className="short-urls-row__cell short-urls-row__cell--break" data-th="Long URL: ">
<ExternalLink href={shortUrl.longUrl} /> <ExternalLink href={shortUrl.longUrl} />
</td> </td>
<td className="short-urls-row__cell" data-th="Tags: ">{this.renderTags(shortUrl.tags)}</td> <td className="short-urls-row__cell" data-th="Tags: ">{renderTags(shortUrl.tags)}</td>
<td className="short-urls-row__cell text-md-right" data-th="Visits: "> <td className="short-urls-row__cell text-md-right" data-th="Visits: ">
<ShortUrlVisitsCount <ShortUrlVisitsCount
visitsCount={shortUrl.visitsCount} visitsCount={shortUrl.visitsCount}
@@ -86,7 +76,11 @@ const ShortUrlsRow = (
</td> </td>
</tr> </tr>
); );
} };
ShortUrlsRowComp.propTypes = propTypes;
return ShortUrlsRowComp;
}; };
export default ShortUrlsRow; export default ShortUrlsRow;

View File

@@ -33,7 +33,7 @@ const provideServices = (bottle, connect) => {
[ 'listShortUrls', 'resetShortUrlParams' ] [ 'listShortUrls', 'resetShortUrlParams' ]
)); ));
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'stateFlagTimeout'); bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout');
bottle.serviceFactory( bottle.serviceFactory(
'ShortUrlsRowMenu', 'ShortUrlsRowMenu',

View File

@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { stateFlagTimeout } from '../utils'; import { stateFlagTimeout, useStateFlagTimeout } from '../utils';
import Storage from './Storage'; import Storage from './Storage';
import ColorGenerator from './ColorGenerator'; import ColorGenerator from './ColorGenerator';
import buildShlinkApiClient from './ShlinkApiClientBuilder'; import buildShlinkApiClient from './ShlinkApiClientBuilder';
@@ -14,6 +14,7 @@ const provideServices = (bottle) => {
bottle.constant('setTimeout', global.setTimeout); bottle.constant('setTimeout', global.setTimeout);
bottle.serviceFactory('stateFlagTimeout', stateFlagTimeout, 'setTimeout'); bottle.serviceFactory('stateFlagTimeout', stateFlagTimeout, 'setTimeout');
bottle.serviceFactory('useStateFlagTimeout', useStateFlagTimeout, 'setTimeout');
}; };
export default provideServices; export default provideServices;

View File

@@ -19,6 +19,16 @@ export const stateFlagTimeout = (setTimeout) => (
setTimeout(() => setState({ [flagName]: !initialValue }), delay); setTimeout(() => setState({ [flagName]: !initialValue }), delay);
}; };
export const useStateFlagTimeout = (setTimeout) => (initialValue = true, delay = DEFAULT_TIMEOUT_DELAY) => {
const [ flag, setFlag ] = useState(initialValue);
const callback = () => {
setFlag(!initialValue);
setTimeout(() => setFlag(initialValue), delay);
};
return [ flag, callback ];
};
export const determineOrderDir = (clickedField, currentOrderField, currentOrderDir) => { export const determineOrderDir = (clickedField, currentOrderField, currentOrderDir) => {
if (currentOrderField !== clickedField) { if (currentOrderField !== clickedField) {
return 'ASC'; return 'ASC';

View File

@@ -12,7 +12,8 @@ describe('<ShortUrlsRow />', () => {
let wrapper; let wrapper;
const mockFunction = () => ''; const mockFunction = () => '';
const ShortUrlsRowMenu = mockFunction; const ShortUrlsRowMenu = mockFunction;
const stateFlagTimeout = jest.fn(); const stateFlagTimeout = jest.fn(() => true);
const useStateFlagTimeout = jest.fn(() => [ false, stateFlagTimeout ]);
const colorGenerator = { const colorGenerator = {
getColorForKey: mockFunction, getColorForKey: mockFunction,
setColorForKey: mockFunction, setColorForKey: mockFunction,
@@ -30,7 +31,7 @@ describe('<ShortUrlsRow />', () => {
}; };
beforeEach(() => { beforeEach(() => {
const ShortUrlsRow = createShortUrlsRow(ShortUrlsRowMenu, colorGenerator, stateFlagTimeout); const ShortUrlsRow = createShortUrlsRow(ShortUrlsRowMenu, colorGenerator, useStateFlagTimeout);
wrapper = shallow( wrapper = shallow(
<ShortUrlsRow shortUrlsListParams={{}} refreshList={mockFunction} selecrtedServer={server} shortUrl={shortUrl} /> <ShortUrlsRow shortUrlsListParams={{}} refreshList={mockFunction} selecrtedServer={server} shortUrl={shortUrl} />
@@ -96,12 +97,4 @@ describe('<ShortUrlsRow />', () => {
menu.simulate('copy'); menu.simulate('copy');
expect(stateFlagTimeout).toHaveBeenCalledTimes(1); expect(stateFlagTimeout).toHaveBeenCalledTimes(1);
}); });
it('shows copy hint when state prop is true', () => {
const isHidden = () => wrapper.find('td').at(1).find('.short-urls-row__copy-hint').prop('hidden');
expect(isHidden()).toEqual(true);
wrapper.setState({ copiedToClipboard: true });
expect(isHidden()).toEqual(false);
});
}); });