Multi-step approval workflow
Select rows from an approval queue and act on them in bulk. The toolbar buttons change based on which rows are selected — already-resolved requests (approved or rejected) are automatically excluded from the action, so reviewers can't accidentally re-process them.
Request
Requester
Dept
Amount
Status
Submitted
Pending
Pending
Needs info
Pending
Approved
Rejected
Pending
Disabling already-resolved rows
Pass selectableRowDisabled to prevent selecting rows that can't be acted on.
The toolbar still shows how many are excluded so nothing is silently skipped:
const ACTIONABLE: Status[] = ['pending', 'needs-info'];
<DataTable
selectableRows
selectableRowDisabled={r => !ACTIONABLE.includes(r.status)}
onSelectedRowsChange={({ selectedRows }) => setSelected(selectedRows)}
/> Context-aware bulk toolbar
Filter the selection down to actionable rows before rendering buttons, and include a count so reviewers know when some rows were skipped:
const actionable = selected.filter(r => ACTIONABLE.includes(r.status));
{selected.length > 0 && (
<div>
<span>{selected.length} selected</span>
{actionable.length < selected.length && (
<span>({selected.length - actionable.length} already resolved, excluded)</span>
)}
{actionable.length > 0 && (
<>
<button onClick={() => applyStatus(actionable.map(r => r.id), 'approved')}>
Approve ({actionable.length})
</button>
<button onClick={() => applyStatus(actionable.map(r => r.id), 'rejected')}>
Reject
</button>
</>
)}
</div>
)} Clearing selection after an action
Use the imperative ref to clear the checkboxes after each bulk action commits:
const ref = useRef<DataTableHandle>(null);
function applyStatus(ids: number[], next: Status) {
setData(prev => prev.map(r => ids.includes(r.id) ? { ...r, status: next } : r));
ref.current?.clearSelectedRows();
}
<DataTable ref={ref} ... />