Relocate columns and row logic, fix columns and useGroupBy to be more pure

Since useColumns was relying on groupBy logic, this was code smell. I wanted useGroupBy to be able to add that logic all by itself and not have to have dependencies in the core of the table.

To fix that, I've moved the core column and row logic to the useTable hook and added a new hook 'columnsBeforeHeaderGroups' to allow useGroupBy to do what i needs in a more pure way.
This commit is contained in:
tannerlinsley
2019-07-29 11:00:07 -06:00
parent cadd8bf62a
commit dc73347003
9 changed files with 587 additions and 451 deletions

View File

@@ -14,14 +14,11 @@ const propTypes = {
// General
columns: PropTypes.arrayOf(
PropTypes.shape({
filterFn: PropTypes.func,
filterAll: PropTypes.bool,
canFilter: PropTypes.bool,
disableFilters: PropTypes.bool,
Filter: PropTypes.any,
})
),
filterFn: PropTypes.func,
manualFilters: PropTypes.bool,
}
@@ -99,12 +96,12 @@ export const useFilters = props => {
hooks.columns.push(columns => {
columns.forEach(column => {
const { id, accessor, canFilter } = column
const { id, accessor, disableFilters: columnDisableFilters } = column
// Determine if a column is filterable
column.canFilter = accessor
? getFirstDefined(
canFilter,
columnDisableFilters,
disableFilters === true ? false : undefined,
true
)
@@ -143,13 +140,12 @@ export const useFilters = props => {
// Find the filters column
const column = columns.find(d => d.id === columnID)
column.preFilteredRows = filteredSoFar
// Don't filter hidden columns or columns that have had their filters disabled
if (!column || column.filterable === false) {
if (!column) {
return filteredSoFar
}
column.preFilteredRows = filteredSoFar
const filterMethod = getFilterMethod(
column.filter,
userFilterTypes || {},

View File

@@ -20,12 +20,13 @@ const propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
aggregate: PropTypes.func,
canGroupBy: PropTypes.bool,
disableGrouping: PropTypes.bool,
Aggregated: PropTypes.any,
})
),
groupByFn: PropTypes.func,
manualGrouping: PropTypes.bool,
disableGrouping: PropTypes.bool,
aggregations: PropTypes.object,
}
@@ -44,13 +45,23 @@ export const useGroupBy = props => {
state: [{ groupBy }, setState],
} = props
// Sort grouped columns to the start of the column list
// before the headers are built
hooks.columnsBeforeHeaderGroups.push(columns => {
return [
...groupBy.map(g => columns.find(col => col.id === g)),
...columns.filter(col => !groupBy.includes(col.id)),
]
})
columns.forEach(column => {
const { id, accessor, canGroupBy } = column
const { id, accessor, disableGrouping: columnDisableGrouping } = column
column.grouped = groupBy.includes(id)
column.groupedIndex = groupBy.indexOf(id)
column.canGroupBy = accessor
? getFirstDefined(
canGroupBy,
columnDisableGrouping,
disableGrouping === true ? false : undefined,
true
)
@@ -115,81 +126,78 @@ export const useGroupBy = props => {
hooks.columns.push(addGroupByToggleProps)
hooks.headers.push(addGroupByToggleProps)
const groupedRows = useMemo(() => {
if (manualGroupBy || !groupBy.length) {
return rows
}
if (debug) console.info('getGroupedRows')
// Find the columns that can or are aggregating
// Uses each column to aggregate rows into a single value
const aggregateRowsToValues = rows => {
const values = {}
columns.forEach(column => {
const columnValues = rows.map(d => d.values[column.id])
let aggregate =
userAggregations[column.aggregate] ||
aggregations[column.aggregate] ||
column.aggregate
if (typeof aggregate === 'function') {
values[column.id] = aggregate(columnValues, rows)
} else if (aggregate) {
throw new Error(
`Invalid aggregate "${aggregate}" passed to column with ID: "${
column.id
}"`
)
} else {
values[column.id] = columnValues[0]
}
})
return values
}
// Recursively group the data
const groupRecursively = (rows, groupBy, depth = 0) => {
// This is the last level, just return the rows
if (depth >= groupBy.length) {
const groupedRows = useMemo(
() => {
if (manualGroupBy || !groupBy.length) {
return rows
}
if (debug) console.info('getGroupedRows')
// Find the columns that can or are aggregating
// Group the rows together for this level
let groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
([groupByVal, subRows], index) => {
// Recurse to sub rows before aggregation
subRows = groupRecursively(subRows, groupBy, depth + 1)
const values = aggregateRowsToValues(subRows)
const row = {
groupByID: groupBy[depth],
groupByVal,
values,
subRows,
depth,
index,
// Uses each column to aggregate rows into a single value
const aggregateRowsToValues = rows => {
const values = {}
columns.forEach(column => {
const columnValues = rows.map(d => d.values[column.id])
let aggregate =
userAggregations[column.aggregate] ||
aggregations[column.aggregate] ||
column.aggregate
if (typeof aggregate === 'function') {
values[column.id] = aggregate(columnValues, rows)
} else if (aggregate) {
throw new Error(
`Invalid aggregate "${aggregate}" passed to column with ID: "${
column.id
}"`
)
} else {
values[column.id] = columnValues[0]
}
return row
})
return values
}
// Recursively group the data
const groupRecursively = (rows, groupBy, depth = 0) => {
// This is the last level, just return the rows
if (depth >= groupBy.length) {
return rows
}
)
return groupedRows
}
// Group the rows together for this level
let groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
([groupByVal, subRows], index) => {
// Recurse to sub rows before aggregation
subRows = groupRecursively(subRows, groupBy, depth + 1)
// Assign the new data
return groupRecursively(rows, groupBy)
}, [
manualGroupBy,
groupBy,
debug,
rows,
columns,
userAggregations,
groupByFn,
])
const values = aggregateRowsToValues(subRows)
const row = {
groupByID: groupBy[depth],
groupByVal,
values,
subRows,
depth,
index,
}
return row
}
)
return groupedRows
}
// Assign the new data
return groupRecursively(rows, groupBy)
},
[manualGroupBy, groupBy, debug, rows, columns, userAggregations, groupByFn]
)
return {
...props,
toggleGroupBy,
rows: groupedRows,
preGroupedRows: rows,
}
}

View File

@@ -22,6 +22,7 @@ const propTypes = {
PropTypes.shape({
sortType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
sortDescFirst: PropTypes.bool,
disableSorting: PropTypes.bool,
})
),
orderByFn: PropTypes.func,
@@ -67,10 +68,10 @@ export const useSortBy = props => {
}
columns.forEach(column => {
const { accessor, canSortBy } = column
const { accessor, disableSorting: columnDisableSorting } = column
column.canSortBy = accessor
? getFirstDefined(
canSortBy,
columnDisableSorting,
disableSorting === true ? false : undefined,
true
)
@@ -78,7 +79,7 @@ export const useSortBy = props => {
})
// Updates sorting based on a columnID, desc flag and multi flag
const toggleSortByID = (columnID, desc, multi) => {
const toggleSortBy = (columnID, desc, multi) => {
return setState(old => {
const { sortBy } = old
@@ -167,7 +168,7 @@ export const useSortBy = props => {
columns.forEach(column => {
if (column.canSortBy) {
column.toggleSortBy = (desc, multi) =>
toggleSortByID(column.id, desc, multi)
toggleSortBy(column.id, desc, multi)
}
})
return columns
@@ -279,6 +280,8 @@ export const useSortBy = props => {
return {
...props,
toggleSortBy,
rows: sortedRows,
preSortedRows: rows,
}
}