From 6d8845e3f9c74a2ca64c60624eb3078de74d7cd3 Mon Sep 17 00:00:00 2001 From: Victor Viale Date: Sun, 26 Apr 2020 17:51:14 +0200 Subject: [PATCH] Translate SortableTable to Typescript This also removes the dependency on recompose :wave: --- packages/core/package.json | 3 +- .../{SortableTable.js => SortableTable.tsx} | 263 ++++++++++-------- yarn.lock | 80 +----- 3 files changed, 158 insertions(+), 188 deletions(-) rename packages/core/src/components/SortableTable/{SortableTable.js => SortableTable.tsx} (54%) diff --git a/packages/core/package.json b/packages/core/package.json index aa3efafa95..a5f7c35c16 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -53,8 +53,7 @@ "react-helmet": "5.2.1", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-sparklines": "^1.7.0", - "recompose": "0.30.0" + "react-sparklines": "^1.7.0" }, "files": [ "dist" diff --git a/packages/core/src/components/SortableTable/SortableTable.js b/packages/core/src/components/SortableTable/SortableTable.tsx similarity index 54% rename from packages/core/src/components/SortableTable/SortableTable.js rename to packages/core/src/components/SortableTable/SortableTable.tsx index ab4e1af2de..4db3aee9ff 100644 --- a/packages/core/src/components/SortableTable/SortableTable.js +++ b/packages/core/src/components/SortableTable/SortableTable.tsx @@ -14,9 +14,7 @@ * limitations under the License. */ -import React from 'react'; -import { pure } from 'recompose'; -import PropTypes from 'prop-types'; +import React, { FC, CSSProperties, memo, useState, useEffect } from 'react'; import { Table, TableBody, @@ -28,11 +26,45 @@ import { Tooltip, } from '@material-ui/core'; +type Column = { + id: string; + label: string; + numeric?: boolean; + disablePadding: boolean; + style: CSSProperties; + // FIXME (@Koroeskohr) + sortValue: (obj: object) => any; +}; + +// XXX (@Koroeskohr): no idea what I did but this typechecks. Need answer for CellContentProps +type Row = { [name in string]: any }; + +type SortHandler = ( + event: React.MouseEvent, + property: string, +) => void; + +type Order = 'asc' | 'desc'; + +type EnhancedTableHeadProps = { + columns: Column[]; + onRequestSort: SortHandler; + order: Order; + orderBy: string; +}; + /** * Table header which supports sorting ascending and desc */ -const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => { - const createSortHandler = property => event => { +const EnhancedTableHead: FC = ({ + columns, + onRequestSort, + order, + orderBy, +}) => { + const createSortHandler = (property: string) => ( + event: React.MouseEvent, + ) => { onRequestSort(event, property); }; @@ -68,25 +100,15 @@ const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => { ); }; -EnhancedTableHead.propTypes = { - columns: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - numeric: PropTypes.bool, - disablePadding: PropTypes.bool, - style: PropTypes.object, - }), - ).isRequired, - onRequestSort: PropTypes.func.isRequired, - order: PropTypes.string.isRequired, - orderBy: PropTypes.string.isRequired, +type CellContentProps = { + // XXX (@Koroeskohr): what am I supposed to use here + data: any | any[]; }; /** * CellContent can be an array or a string */ -const CellContent = ({ data }) => { +const CellContent: FC = ({ data }) => { if (Array.isArray(data)) { return data.map((item, index) => ( @@ -98,11 +120,12 @@ const CellContent = ({ data }) => { return data; }; -CellContent.propTypes = { - data: PropTypes.any.isRequired, +type DataTableCellProps = { + column: Column; + row: Row; }; -const DataTableCell = ({ column, row }) => { +const DataTableCell: FC = ({ column, row }) => { return ( { }; const noop = () => {}; -const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { - const onClick = event => (handleRowClick || noop)(event, row.id); +type DataTableRowProps = { + row: Row; + columns: Column[]; + handleRowClick?: (event: React.MouseEvent, rowId: string) => void; + style?: React.CSSProperties; +}; +const _DataTableRow: FC = ({ + row, + columns, + handleRowClick, + style, +}) => { + const onClick: React.MouseEventHandler = event => + (handleRowClick || noop)(event, row.id); return ( {columns.map(column => ( @@ -124,17 +159,18 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { ))} ); -}); +}; +const DataTableRow = memo(_DataTableRow); /** * Table with sorting capabilites automatic rendering of cells * Note that the objects in props.data needs have an id property * The columns array defines which columns from the data to show. * - * @param {Array[Object]} data A list of data entries, where object properties must + * @param data A list of data entries, where object properties must * be strictly equal to column ids. * - * @param {Array[Object]} columns A list of columns with the following shape: + * @param columns A list of columns with the following shape: * { * // The column identifier must be strictly equal the relevant data entry * // key: @@ -154,17 +190,17 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { * sortValue: (Object) => Any * } * - * @param {String} orderBy The column ID initially used for sorting + * @param orderBy The column ID initially used for sorting * - * @param {String} [dataVersion] A version identifier for the data which *must* + * @param dataVersion A version identifier for the data which *must* * be updated when the contents of the data changes. This can be used for * components where the same SortableTable element will be used to display * variable sets of data. * - * @param {Array[Object]} [footerData] A list of data entries to be placed in + * @param footerData A list of data entries to be placed in * the table footer, which will not be sorted. * - * @param {(String, Event) => Void} [onRowClicked] Get notified when a user clicks + * @param onRowClicked Get notified when a user clicks * on the row. The handler will receive the row id as the first argument, and * the synthetic click event as the second argument. * @@ -188,49 +224,38 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { * ev.preventDefault();}}/>) * } * + * // XXX (@koroeskohr): supposedly this is leftover from your internal doc * @deprecated use shared/components/DataGrid */ -class SortableTable extends React.Component { - static propTypes = { - // TODO: figure out how to make id of the object requried while others are dynamic - data: PropTypes.arrayOf(PropTypes.object).isRequired, - orderBy: PropTypes.string.isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - onRowClicked: PropTypes.func, - dataVersion: PropTypes.string, - }; - constructor(props) { - super(props); - this.handleRowClick = this.handleRowClick.bind(this); +type SortableTableProps = { + data: Row[]; + footerData: Row[]; + orderBy: string; + columns: Column[]; + onRowClicked?: ( + id: string, + event: React.MouseEvent, + ) => void; + dataVersion: string; +}; - this.state = { - orderBy: props.orderBy, - order: 'asc', - data: props.data, - }; - } +type TableState = { + orderBy: string; + order: Order; + data: Row[]; +}; - handleRequestSort = (event, property) => { - event.preventDefault(); - const orderBy = property; - let order = 'desc'; - if (this.state.orderBy === property && this.state.order === 'desc') { - order = 'asc'; - } - this.updateData(this.state.data, orderBy, order); - }; +const SortableTable: FC = props => { + const [state, setState] = useState({ + orderBy: props.orderBy, + order: 'asc', + data: props.data, + }); - handleRowClick = (event, id) => { - if (this.props.onRowClicked) { - this.props.onRowClicked(id, event); - } - }; - - updateData = (data, orderBy, order) => { - const sortValueFn = ( - this.props.columns.filter(col => col.id === orderBy)[0] || {} - ).sortValue; + const updateData = (data: Row[], orderBy: string, order: Order) => { + const sortValueFn = (props.columns.find(col => col.id === orderBy) || {}) + .sortValue; const sortedData = data.slice().sort((a, b) => { const valueA = sortValueFn ? sortValueFn(a) : a[orderBy]; @@ -241,56 +266,70 @@ class SortableTable extends React.Component { if (valueB === '' || valueB === null) return -inc; return valueA < valueB ? -inc : inc; }); - this.setState({ data: sortedData, order, orderBy }); + setState({ data: sortedData, order, orderBy }); }; - UNSAFE_componentWillReceiveProps(props) { - if (props.dataVersion !== this.props.dataVersion) { - this.updateData(props.data, this.state.orderBy, this.state.order); + const handleRequestSort: SortHandler = (event, property) => { + event.preventDefault(); + const orderBy = property; + let order: Order = 'desc'; + if (state.orderBy === property && state.order === 'desc') { + order = 'asc'; } - } + updateData(state.data, orderBy, order); + }; - render() { - const { data, order, orderBy } = this.state; - const { columns, dataVersion, footerData } = this.props; - - let tableFoot = null; - if (footerData && footerData.length > 0) { - tableFoot = ( - - {footerData.map(row => ( - - ))} - - ); + const handleRowClick = (event: React.MouseEvent, id: string): void => { + if (props.onRowClicked) { + props.onRowClicked(id, event); } - return ( - - - - {data.map(row => ( - - ))} - - {tableFoot} -
+ }; + + useEffect(() => { + const { data } = props; + const { orderBy, order } = state; + updateData(data, orderBy, order); + }, [props.dataVersion]); + + const { data, order, orderBy } = state; + const { columns, dataVersion, footerData } = props; + + let tableFoot = null; + if (footerData && footerData.length > 0) { + tableFoot = ( + + {footerData.map(row => ( + + ))} + ); } -} + return ( + + + + {data.map(row => ( + + ))} + + {tableFoot} +
+ ); +}; export default SortableTable; diff --git a/yarn.lock b/yarn.lock index 5126cfa721..df37b90d8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5133,7 +5133,7 @@ arrify@^1.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: +asap@^2.0.0, asap@~2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -6333,11 +6333,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-emitter@^0.1.2: - version "0.1.6" - resolved "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" - integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= - character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" @@ -7107,11 +7102,6 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - core-js@^2.4.0, core-js@^2.5.0: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -9204,19 +9194,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbjs@^0.8.1: - version "0.8.17" - resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -10429,11 +10406,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1: - version "2.5.5" - resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -11543,7 +11515,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -11656,14 +11628,6 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -14363,14 +14327,6 @@ node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" @@ -16569,13 +16525,6 @@ promise.series@^0.2.0: resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - promise@^8.0.3: version "8.1.0" resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -17039,7 +16988,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -17477,18 +17426,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -recompose@0.30.0: - version "0.30.0" - resolved "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" - integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== - dependencies: - "@babel/runtime" "^7.0.0" - change-emitter "^0.1.2" - fbjs "^0.8.1" - hoist-non-react-statics "^2.3.1" - react-lifecycles-compat "^3.0.2" - symbol-observable "^1.0.4" - recursive-readdir@2.2.2, recursive-readdir@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -18395,7 +18332,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -19394,7 +19331,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -20021,11 +19958,6 @@ typescript@^3.7.4, typescript@^3.8.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@^0.7.18: - version "0.7.21" - resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== - uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" @@ -20708,7 +20640,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@3.0.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==