Skip to content

Commit

Permalink
Refactor table sort (#437)
Browse files Browse the repository at this point in the history
* Initial refactor or SortableTable to not use sortable() HOC

* Refactored value getter

* Added stories and tests for initial column sort

* Update version

* Updates with review comments

* Additional review comments

* Trigger travis

Co-authored-by: David Pickart <[email protected]>
  • Loading branch information
mwislek and dpikt authored Jul 24, 2020
1 parent 7d2b33a commit cda5b21
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 147 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "3.32.0",
"version": "3.32.1",
"engines": {
"node": "^8.0.0 || ^10.13.0 || ^12.0.0"
},
Expand Down
4 changes: 2 additions & 2 deletions src/tables/components/table-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ function TableRow ({
const { name, component: CellComponent=DefaultCellComponent, format=identity, onClick=noop, valueGetter, ...rest } = column
const cellValue =
valueGetter ? valueGetter(rowData) : get(name, rowData)
const value = format(cellValue)
const formattedValue = format(cellValue)
const onColClick = column.disabled ? noop : () => onClick(rowData)
return <CellComponent { ...{ // eslint-disable-line
key,
value,
value: formattedValue,
name,
data: rowData,
onClick: onColClick,
Expand Down
2 changes: 1 addition & 1 deletion src/tables/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { columnPropTypes, Types } from './column-prop-types'
export getColumnData from './get-column-data'
export getColumnData from './get-column-data'
170 changes: 103 additions & 67 deletions src/tables/sortable-table.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import React, { useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import { sortable, sortablePropTypes, noop } from '../utils'
import { getColumnData, Types } from './helpers'
import { TableHeader as DefaultHeader, TableRow as Row } from './components'
import { get, noop, orderBy } from '../utils'
import classnames from 'classnames'

/**
Expand Down Expand Up @@ -36,37 +36,118 @@ import classnames from 'classnames'
/* eslint react/jsx-key: 0 */

const propTypes = {
columns: PropTypes.arrayOf(Types.column).isRequired,
controlled: PropTypes.bool.isRequired,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
disableSort: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
className: PropTypes.string,
data: PropTypes.arrayOf(PropTypes.object),
initialAscending: PropTypes.bool,
initialColumn: PropTypes.string,
disableReverse: PropTypes.bool,
disableSort: PropTypes.bool,
controlled: PropTypes.bool,
onChange: PropTypes.func,
rowComponent: Types.component,
headerComponent: Types.component,
className: PropTypes.string,
...sortablePropTypes,
}

const defaultProps = {
className: ''
className: '',
data: [],
initialAscending: true,
initialColumn: '',
disableReverse: false,
disableSort: false,
controlled: false,
onChange: noop,
}
const defaultControls = {
initialSortPath: '',
initialSortFunc: null,
initialValueGetter: null,
}

function getInitialSortControls(initialColumn, columns) {
if (!initialColumn) return defaultControls

const initialProps = columns.filter(col => col.name === initialColumn).pop()
// Exceptional situation-- an initial column was specified but no column data
// exists for the named column...
if (!initialProps) throw new Error('initial column has no column definition')

return {
initialSortPath: initialProps.name,
initialSortFunc: initialProps.sortFunc,
initialValueGetter: initialProps.valueGetter,
}
}


function SortableTable ({
columns,
function SortableTable({
className,
children,
data: unsortedData,
initialAscending,
initialColumn,
disableReverse,
disableSort,
controlled,
sort,
ascending,
sortPath,
setSortPath,
setSortFunc,
onChange,
rowComponent,
headerComponent,
className,
}) {
const data = (controlled || disableSort) ? unsortedData : sort(unsortedData)
const columns = getColumnData(children, disableSort)
const { initialSortPath, initialSortFunc, initialValueGetter } =
getInitialSortControls(initialColumn, columns)
const [ascending, setAscending] = useState(initialAscending)
const [sortPath, setSortPath] = useState(initialSortPath)

// Setting and storing a function object requires special syntax.
// See: https://medium.com/swlh/how-to-store-a-function-with-the-usestate-hook-in-react-8a88dd4eede1
const [sortFunc, setSortFunc] = useState(() => initialSortFunc)
const [valueGetter, setValueGetter] = useState(() => initialValueGetter)

const data = useMemo(() => {
if (controlled || disableSort) return unsortedData

if (sortFunc) {
const sorted = [...unsortedData].sort(sortFunc)
if (!ascending && !disableReverse) sorted.reverse()
return sorted
}
else {
const order = ascending ? 'asc' : 'desc'
const sorted = orderBy(
unsortedData,
(item) => valueGetter ? valueGetter(item) : get(sortPath, item),
order
)
return sorted
}
}, [ascending, sortPath, sortFunc, valueGetter])

const handleColumnChange = (column) => {
if (column.disabled) return

const newSortPath = column.name
const newSortFunc = column.sortFunc || null
const newValueGetter = column.valueGetter || null

// Toggle ascending if the path is already selected. Otherwise, default
// to ascending when switching paths...
const newAscending = newSortPath === sortPath ? !ascending : true

setAscending(newAscending)
setSortPath(newSortPath)
setSortFunc(() => newSortFunc)
setValueGetter(() => newValueGetter)

if (onChange) onChange({
ascending: newAscending,
sortPath: newSortPath,
sortFunc: newSortFunc
})
}

return (
<table className={ classnames(className, { 'sortable-table': !disableSort }) }>
<table className={classnames(className, { 'sortable-table': !disableSort })}>
<thead><tr>
{
columns.map((column, key) => {
Expand All @@ -77,13 +158,7 @@ function SortableTable ({
column,
sortPath,
ascending,
onClick: () => {
if (column.disabled) return
const newSortPath = column.name
const newSortFunc = column.sortFunc || null
setSortPath(newSortPath)
setSortFunc(newSortFunc)
}
onClick: () => handleColumnChange(column)
}} />
)
}
Expand All @@ -109,43 +184,4 @@ function SortableTable ({
SortableTable.propTypes = propTypes
SortableTable.defaultProps = defaultProps

const WrappedTable = sortable()(SortableTable)

// Passes relevant sortable props
function SortableTableWrapper ({ initialColumn, children, disableSort, disableReverse, onChange, ...rest }) {
const columns = getColumnData(children, disableSort)
const initialProps = columns.filter(col => col.name === initialColumn).pop()
return <WrappedTable {...{
// Sortable props
initialSortPath: initialProps ? initialProps.name : '',
initialSortFunc: initialProps ? initialProps.sortFunc : null,
onChange,
disableReverse,
// Local props
columns,
disableSort,
...rest,
}} />
}

SortableTableWrapper.propTypes = {
initialColumn: PropTypes.string,
children: PropTypes.node.isRequired,
data: PropTypes.arrayOf(PropTypes.object),
disableSort: PropTypes.bool,
disableReverse: PropTypes.bool,
onChange: PropTypes.func,
rowComponent: Types.component,
headerComponent: Types.component,
}

SortableTableWrapper.defaultProps = {
initialColumn: '',
disableSort: false,
controlled: false,
disableReverse: false,
data: [],
onChange: noop,
}

export default SortableTableWrapper
export default SortableTable
1 change: 1 addition & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {
startCase,
range,
noop,
orderBy,
first,
toLower,
union as addToArray,
Expand Down
Loading

0 comments on commit cda5b21

Please sign in to comment.