Reputation: 727
does anyone know a way to select multiple rows with React-Table. Say if we wish to click a cell in a row and then press SHIFT and then select another row and perhaps color code the selected rows with CSS? Is this even possible?
Upvotes: 7
Views: 10915
Reputation: 1407
I built this out with react-table 6.x. I'm using the checkbox table HOC. My enclosing component is a functional component using hooks. It handles shift-click in both directions (top to bottom, bottom to top) and handles shift-unselect.
Basic component setup:
import checkboxHOC from 'react-table/lib/hoc/selectTable'
// see "selectTable" - https://github.com/TanStack/table/blob/v6.8.6/src/hoc/README.md
const CheckboxTable = checkboxHOC(ReactTable)
const TableElement = CheckboxTable
const selection = .... // some array of primary keys
const [lastPrimaryKeySelected, setLastPrimaryKeySelected] = React.useState(null)
return <TableElement toggleSelection={toggleSelection} ... />
Then the biz logic:
const toggleSelection = React.useCallback(
(id, e) => {
const currentlyChecking = !selection.has(id)
const previouslyChecking = selection.has(lastPrimaryKeySelected)
if (e.shiftKey && lastPrimaryKeySelected && previouslyChecking === currentlyChecking) {
// Support shift-click. (Check all locations from lastPrimaryKeySelected to id)
// This ^ also avoids doing anything if they select a box then shift-unselect another box.
toggleRange(lastPrimaryKeySelected, id, currentlyChecking)
} else {
toggleRange(id, id, currentlyChecking)
}
setLastPrimaryKeySelected(id)
},
[selection, lastPrimaryKeySelected, toggleRange]
)
// Unconditionally check or uncheck the range.
// checkRange - when false, this will uncheck the range.
// primaryKeyA / primaryKeyB - the order isn't important.
const toggleRange = React.useCallback(
(primaryKeyA, primaryKeyB, checkRange) => {
const setCopy = new Set(selection)
// keys are primary keys of some record
// values are the index of internalData
const primaryKeyToIndex = {}
// Seems sketchy but the official examples demonstrate this: https://github.com/TanStack/table/blob/v6.8.6/docs/src/examples/selecttable/index.js#L104-L111
// https://stackoverflow.com/a/52986052
const internalData = reactTable.current.getWrappedInstance().getResolvedState().sortedData
internalData?.forEach((d, index) => {
primaryKeyToIndex[d._original.id] = index
})
// potential (but complicated) optimization here ^: only fill out primaryKeyToIndex for the current page being viewed.
// It currently holds the entire list of records
// Complicated because react-table doesn't seem to expose the current page worth of data, so we'd have to calculate offsets ourselves.
// `sort` makes the following for-loop simpler. Handles shift-clicking from top to bottom, or bottom to top.
const [startIndex, endIndex] = [primaryKeyToIndex[primaryKeyA], primaryKeyToIndex[primaryKeyB]].sort(
(a, b) => a - b // JS can't sort numbers correctly without a comparator function
)
for (let i = startIndex; i <= endIndex; i += 1) {
const primaryKeyForIndex = internalData[i]._original.id
if (checkRange) {
setCopy.add(primaryKeyForIndex) // handle shift-select
} else {
setCopy.delete(primaryKeyForIndex) // handle shift-unselect
}
}
onSelectionChange(setCopy)
},
[onSelectionChange, selection]
)
Upvotes: 0
Reputation: 504
I found a way to do it. Let me know if you have any questions. Basically just need to implement on your own.
state = {
selected: null,
selectedRows: [],
}
previousRow = null;
handleClick = (state, rowInfo) => {
if (rowInfo) {
return {
onClick: (e) => {
let selectedRows = [];
// if shift key is being pressed upon click and there is a row that was previously selected, grab all rows inbetween including both previous and current clicked rows
if (e.shiftKey && this.previousRow) {
// if previous row is above current row in table
if (this.previousRow.index < rowInfo.index) {
for (let i = this.previousRow.index; i <= rowInfo.index; i++) {
selectedRows.push(state.sortedData[i]);
}
// or the opposite
} else {
for (let i = rowInfo.index; i <= this.previousRow.index; i++) {
selectedRows.push(state.sortedData[i]);
}
}
} else {
// add _index field so this entry is same as others from sortedData
rowInfo._index = rowInfo.index;
selectedRows.push(rowInfo);
this.previousRow = rowInfo;
}
this.setState({ selected: rowInfo.index, selectedRows })
},
style: {
// check index of rows in selectedRows against all rowInfo's indices, to match them up, and return true/highlight row, if there is a index from selectedRows in rowInfo/the table data
background: this.state.selectedRows.some((e) => e._index === rowInfo.index) && '#9bdfff',
}
}
} else {
return {}
}
Upvotes: 12