From 16d96efa4a5b8d885daef48107bec25e94bc6d9f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 31 Aug 2020 18:38:27 +0200 Subject: [PATCH] Finished migrating all remaining utils to TS --- src/short-urls/SearchBar.tsx | 6 +-- src/short-urls/ShortUrlsList.tsx | 4 +- .../{DateRangeRow.js => DateRangeRow.tsx} | 27 +++++++------ ...alFormGroup.js => HorizontalFormGroup.tsx} | 25 ++++++------ src/utils/{Message.js => Message.tsx} | 36 +++++++++--------- ...tionDropdown.js => PaginationDropdown.tsx} | 17 ++++----- src/utils/{SearchField.js => SearchField.tsx} | 27 +++++++------ ...SortingDropdown.js => SortingDropdown.tsx} | 38 ++++++++----------- src/utils/utils.ts | 6 ++- ...RangeRow.test.js => DateRangeRow.test.tsx} | 4 +- ...pdown.test.js => SortingDropdown.test.tsx} | 10 ++--- 11 files changed, 95 insertions(+), 105 deletions(-) rename src/utils/{DateRangeRow.js => DateRangeRow.tsx} (55%) rename src/utils/{HorizontalFormGroup.js => HorizontalFormGroup.tsx} (50%) rename src/utils/{Message.js => Message.tsx} (55%) rename src/utils/{PaginationDropdown.js => PaginationDropdown.tsx} (73%) rename src/utils/{SearchField.js => SearchField.tsx} (72%) rename src/utils/{SortingDropdown.js => SortingDropdown.tsx} (70%) rename test/utils/{DateRangeRow.test.js => DateRangeRow.test.tsx} (93%) rename test/utils/{SortingDropdown.test.js => SortingDropdown.test.tsx} (90%) diff --git a/src/short-urls/SearchBar.tsx b/src/short-urls/SearchBar.tsx index 85150608..5e9b44b9 100644 --- a/src/short-urls/SearchBar.tsx +++ b/src/short-urls/SearchBar.tsx @@ -17,7 +17,7 @@ interface SearchBarProps { shortUrlsListParams: ShortUrlsListParams; } -const dateOrUndefined = (date?: string) => date ? moment(date) : undefined; +const dateOrNull = (date?: string) => date ? moment(date) : null; const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC) => ( { listShortUrls, shortUrlsListParams }: SearchBarProps, @@ -41,8 +41,8 @@ const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC
diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index 1273894d..c045c892 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -62,9 +62,9 @@ const ShortUrlsList = (ShortUrlsRow: FC) => ({ orderDir: orderBy && head(values(orderBy)), }); const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); - const handleOrderBy = (orderField: OrderableFields, orderDir: OrderDir) => { + const handleOrderBy = (orderField?: OrderableFields, orderDir?: OrderDir) => { setOrder({ orderField, orderDir }); - refreshList({ orderBy: { [orderField]: orderDir } }); + refreshList({ orderBy: orderField ? { [orderField]: orderDir } : undefined }); }; const orderByColumn = (field: OrderableFields) => () => handleOrderBy(field, determineOrderDir(field, order.orderField, order.orderDir)); diff --git a/src/utils/DateRangeRow.js b/src/utils/DateRangeRow.tsx similarity index 55% rename from src/utils/DateRangeRow.js rename to src/utils/DateRangeRow.tsx index ca547e4a..c96d1af9 100644 --- a/src/utils/DateRangeRow.js +++ b/src/utils/DateRangeRow.tsx @@ -1,25 +1,26 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import moment from 'moment'; import DateInput from './DateInput'; import './DateRangeRow.scss'; -const dateType = PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]); -const propTypes = { - startDate: dateType, - endDate: dateType, - onStartDateChange: PropTypes.func.isRequired, - onEndDateChange: PropTypes.func.isRequired, - disabled: PropTypes.bool, -}; +interface DateRangeRowProps { + startDate?: moment.Moment | null; + endDate?: moment.Moment | null; + onStartDateChange: (date: moment.Moment | null) => void; + onEndDateChange: (date: moment.Moment | null) => void; + disabled?: boolean; +} -const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange, disabled = false }) => ( +const DateRangeRow = ( + { startDate = null, endDate = null, disabled = false, onStartDateChange, onEndDateChange }: DateRangeRowProps, +) => (
@@ -30,7 +31,7 @@ const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange, selected={endDate} placeholderText="Until" isClearable - minDate={startDate} + minDate={startDate ?? undefined} disabled={disabled} onChange={onEndDateChange} /> @@ -38,6 +39,4 @@ const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange,
); -DateRangeRow.propTypes = propTypes; - export default DateRangeRow; diff --git a/src/utils/HorizontalFormGroup.js b/src/utils/HorizontalFormGroup.tsx similarity index 50% rename from src/utils/HorizontalFormGroup.js rename to src/utils/HorizontalFormGroup.tsx index 2586c9c3..7d53c9ed 100644 --- a/src/utils/HorizontalFormGroup.js +++ b/src/utils/HorizontalFormGroup.tsx @@ -1,17 +1,18 @@ -import React from 'react'; +import React, { FC } from 'react'; import { v4 as uuid } from 'uuid'; -import PropTypes from 'prop-types'; +import { InputType } from 'reactstrap/lib/Input'; -const propTypes = { - children: PropTypes.node.isRequired, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - id: PropTypes.string, - type: PropTypes.string, - required: PropTypes.bool, -}; +interface HorizontalFormGroupProps { + value: string; + onChange: (newValue: string) => void; + id?: string; + type?: InputType; + required?: boolean; +} -export const HorizontalFormGroup = ({ children, value, onChange, id = uuid(), type = 'text', required = true }) => ( +export const HorizontalFormGroup: FC = ( + { children, value, onChange, id = uuid(), type = 'text', required = true }, +) => (
); - -HorizontalFormGroup.propTypes = propTypes; diff --git a/src/utils/Message.js b/src/utils/Message.tsx similarity index 55% rename from src/utils/Message.js rename to src/utils/Message.tsx index 2aa723f8..daf9e48e 100644 --- a/src/utils/Message.js +++ b/src/utils/Message.tsx @@ -1,33 +1,35 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Card } from 'reactstrap'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -const getClassForType = (type) => { - const map = { +type MessageType = 'default' | 'error'; + +const getClassForType = (type: MessageType) => { + const map: Record = { error: 'border-danger', + default: '', }; - return map[type] || ''; + return map[type]; }; -const getTextClassForType = (type) => { - const map = { +const getTextClassForType = (type: MessageType) => { + const map: Record = { error: 'text-danger', + default: 'text-muted', }; - return map[type] || 'text-muted'; + return map[type]; }; -const propTypes = { - noMargin: PropTypes.bool, - loading: PropTypes.bool, - children: PropTypes.node, - type: PropTypes.oneOf([ 'default', 'error' ]), -}; +interface MessageProps { + noMargin?: boolean; + loading?: boolean; + type?: MessageType; +} -const Message = ({ children, loading = false, noMargin = false, type = 'default' }) => { +const Message: FC = ({ children, loading = false, noMargin = false, type = 'default' }) => { const cardClasses = classNames('bg-light', getClassForType(type), { 'mt-4': !noMargin }); return ( @@ -35,7 +37,7 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default'

{loading && } - {loading && {children || 'Loading...'}} + {loading && {children ?? 'Loading...'}} {!loading && children}

@@ -43,6 +45,4 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default' ); }; -Message.propTypes = propTypes; - export default Message; diff --git a/src/utils/PaginationDropdown.js b/src/utils/PaginationDropdown.tsx similarity index 73% rename from src/utils/PaginationDropdown.js rename to src/utils/PaginationDropdown.tsx index 0789e9bf..077354f3 100644 --- a/src/utils/PaginationDropdown.js +++ b/src/utils/PaginationDropdown.tsx @@ -1,15 +1,14 @@ import React from 'react'; import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; -import * as PropTypes from 'prop-types'; -const propTypes = { - toggleClassName: PropTypes.string, - ranges: PropTypes.arrayOf(PropTypes.number).isRequired, - value: PropTypes.number.isRequired, - setValue: PropTypes.func.isRequired, -}; +interface PaginationDropdownProps { + ranges: number[]; + value: number; + setValue: (newValue: number) => void; + toggleClassName?: string; +} -const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }) => ( +const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }: PaginationDropdownProps) => ( Paginate @@ -28,6 +27,4 @@ const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }) => ( ); -PaginationDropdown.propTypes = propTypes; - export default PaginationDropdown; diff --git a/src/utils/SearchField.js b/src/utils/SearchField.tsx similarity index 72% rename from src/utils/SearchField.js rename to src/utils/SearchField.tsx index 2b460fa8..7b666a56 100644 --- a/src/utils/SearchField.js +++ b/src/utils/SearchField.tsx @@ -1,29 +1,30 @@ import React, { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch as searchIcon } from '@fortawesome/free-solid-svg-icons'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import './SearchField.scss'; const DEFAULT_SEARCH_INTERVAL = 500; -let timer; +let timer: NodeJS.Timeout | null; -const propTypes = { - onChange: PropTypes.func.isRequired, - className: PropTypes.string, - placeholder: PropTypes.string, - large: PropTypes.bool, - noBorder: PropTypes.bool, -}; +interface SearchField { + onChange: (value: string) => void; + className?: string; + placeholder?: string; + large?: boolean; + noBorder?: boolean; +} -const SearchField = ({ onChange, className, placeholder = 'Search...', large = true, noBorder = false }) => { +const SearchField = ( + { onChange, className, placeholder = 'Search...', large = true, noBorder = false }: SearchField, +) => { const [ searchTerm, setSearchTerm ] = useState(''); const resetTimer = () => { - clearTimeout(timer); + timer && clearTimeout(timer); timer = null; }; - const searchTermChanged = (newSearchTerm, timeout = DEFAULT_SEARCH_INTERVAL) => { + const searchTermChanged = (newSearchTerm: string, timeout = DEFAULT_SEARCH_INTERVAL) => { setSearchTerm(newSearchTerm); resetTimer(); @@ -59,6 +60,4 @@ const SearchField = ({ onChange, className, placeholder = 'Search...', large = t ); }; -SearchField.propTypes = propTypes; - export default SearchField; diff --git a/src/utils/SortingDropdown.js b/src/utils/SortingDropdown.tsx similarity index 70% rename from src/utils/SortingDropdown.js rename to src/utils/SortingDropdown.tsx index 0f123ca2..c85c8bb4 100644 --- a/src/utils/SortingDropdown.js +++ b/src/utils/SortingDropdown.tsx @@ -1,28 +1,25 @@ import React from 'react'; import { UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import { toPairs } from 'ramda'; -import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSortAmountUp as sortAscIcon, faSortAmountDown as sortDescIcon } from '@fortawesome/free-solid-svg-icons'; import classNames from 'classnames'; -import { determineOrderDir } from './utils'; +import { determineOrderDir, OrderDir } from './utils'; import './SortingDropdown.scss'; -const propTypes = { - items: PropTypes.object.isRequired, - orderField: PropTypes.string, - orderDir: PropTypes.oneOf([ 'ASC', 'DESC' ]), - onChange: PropTypes.func.isRequired, - isButton: PropTypes.bool, - right: PropTypes.bool, -}; -const defaultProps = { - isButton: true, - right: false, -}; +export interface SortingDropdownProps { + items: Record; + orderField?: T; + orderDir?: OrderDir; + onChange: (orderField?: T, orderDir?: OrderDir) => void; + isButton?: boolean; + right?: boolean; +} -const SortingDropdown = ({ items, orderField, orderDir, onChange, isButton, right }) => { - const handleItemClick = (fieldKey) => () => { +export default function SortingDropdown( + { items, orderField, orderDir, onChange, isButton = true, right = false }: SortingDropdownProps, +) { + const handleItemClick = (fieldKey: T) => () => { const newOrderDir = determineOrderDir(fieldKey, orderField, orderDir); onChange(newOrderDir ? fieldKey : undefined, newOrderDir); @@ -42,7 +39,7 @@ const SortingDropdown = ({ items, orderField, orderDir, onChange, isButton, righ className={classNames('sorting-dropdown__menu', { 'sorting-dropdown__menu--link': !isButton })} > {toPairs(items).map(([ fieldKey, fieldValue ]) => ( - + {fieldValue} {orderField === fieldKey && ( ); -}; - -SortingDropdown.propTypes = propTypes; -SortingDropdown.defaultProps = defaultProps; - -export default SortingDropdown; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e3a061e4..25c22222 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,11 @@ import { SyntheticEvent } from 'react'; export type OrderDir = 'ASC' | 'DESC' | undefined; -export const determineOrderDir = (currentField: string, newField?: string, currentOrderDir?: OrderDir): OrderDir => { +export const determineOrderDir = ( + currentField: T, + newField?: T, + currentOrderDir?: OrderDir, +): OrderDir => { if (currentField !== newField) { return 'ASC'; } diff --git a/test/utils/DateRangeRow.test.js b/test/utils/DateRangeRow.test.tsx similarity index 93% rename from test/utils/DateRangeRow.test.js rename to test/utils/DateRangeRow.test.tsx index 5b6a4cc5..6c06135f 100644 --- a/test/utils/DateRangeRow.test.js +++ b/test/utils/DateRangeRow.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import DateRangeRow from '../../src/utils/DateRangeRow'; import DateInput from '../../src/utils/DateInput'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const onEndDateChange = jest.fn(); const onStartDateChange = jest.fn(); diff --git a/test/utils/SortingDropdown.test.js b/test/utils/SortingDropdown.test.tsx similarity index 90% rename from test/utils/SortingDropdown.test.js rename to test/utils/SortingDropdown.test.tsx index 1547d31c..3f697a74 100644 --- a/test/utils/SortingDropdown.test.js +++ b/test/utils/SortingDropdown.test.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { DropdownItem } from 'reactstrap'; import { identity, values } from 'ramda'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSortAmountDown as caretDownIcon } from '@fortawesome/free-solid-svg-icons'; -import SortingDropdown from '../../src/utils/SortingDropdown'; +import SortingDropdown, { SortingDropdownProps } from '../../src/utils/SortingDropdown'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const items = { foo: 'Foo', bar: 'Bar', baz: 'Hello World', }; - const createWrapper = (props) => { + const createWrapper = (props: Partial = {}) => { wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('properly renders provided list of items', () => { const wrapper = createWrapper();