material-react-table
Version:
A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.
318 lines (304 loc) • 11.7 kB
text/typescript
import { useMemo, useRef, useState } from 'react';
import { useReactTable } from '@tanstack/react-table';
import {
type MRT_Cell,
type MRT_Column,
type MRT_ColumnDef,
type MRT_ColumnFilterFnsState,
type MRT_ColumnOrderState,
type MRT_ColumnSizingInfoState,
type MRT_DefinedTableOptions,
type MRT_DensityState,
type MRT_FilterOption,
type MRT_GroupingState,
type MRT_PaginationState,
type MRT_Row,
type MRT_RowData,
type MRT_StatefulTableOptions,
type MRT_TableInstance,
type MRT_TableState,
type MRT_Updater,
} from '../types';
import {
getAllLeafColumnDefs,
getColumnId,
getDefaultColumnFilterFn,
prepareColumns,
} from '../utils/column.utils';
import {
getDefaultColumnOrderIds,
showRowActionsColumn,
showRowDragColumn,
showRowExpandColumn,
showRowNumbersColumn,
showRowPinningColumn,
showRowSelectionColumn,
showRowSpacerColumn,
} from '../utils/displayColumn.utils';
import { createRow } from '../utils/tanstack.helpers';
import { getMRT_RowActionsColumnDef } from './display-columns/getMRT_RowActionsColumnDef';
import { getMRT_RowDragColumnDef } from './display-columns/getMRT_RowDragColumnDef';
import { getMRT_RowExpandColumnDef } from './display-columns/getMRT_RowExpandColumnDef';
import { getMRT_RowNumbersColumnDef } from './display-columns/getMRT_RowNumbersColumnDef';
import { getMRT_RowPinningColumnDef } from './display-columns/getMRT_RowPinningColumnDef';
import { getMRT_RowSelectColumnDef } from './display-columns/getMRT_RowSelectColumnDef';
import { getMRT_RowSpacerColumnDef } from './display-columns/getMRT_RowSpacerColumnDef';
import { useMRT_Effects } from './useMRT_Effects';
/**
* The MRT hook that wraps the TanStack useReactTable hook and adds additional functionality
* @param definedTableOptions - table options with proper defaults set
* @returns the MRT table instance
*/
export const useMRT_TableInstance = <TData extends MRT_RowData>(
definedTableOptions: MRT_DefinedTableOptions<TData>,
): MRT_TableInstance<TData> => {
const lastSelectedRowId = useRef<null | string>(null);
const actionCellRef = useRef<HTMLTableCellElement>(null);
const bottomToolbarRef = useRef<HTMLDivElement>(null);
const editInputRefs = useRef<Record<string, HTMLInputElement>>({});
const filterInputRefs = useRef<Record<string, HTMLInputElement>>({});
const searchInputRef = useRef<HTMLInputElement>(null);
const tableContainerRef = useRef<HTMLDivElement>(null);
const tableHeadCellRefs = useRef<Record<string, HTMLTableCellElement>>({});
const tablePaperRef = useRef<HTMLDivElement>(null);
const topToolbarRef = useRef<HTMLDivElement>(null);
const tableHeadRef = useRef<HTMLTableSectionElement>(null);
const tableFooterRef = useRef<HTMLTableSectionElement>(null);
//transform initial state with proper column order
const initialState: Partial<MRT_TableState<TData>> = useMemo(() => {
const initState = definedTableOptions.initialState ?? {};
initState.columnOrder =
initState.columnOrder ??
getDefaultColumnOrderIds({
...definedTableOptions,
state: {
...definedTableOptions.initialState,
...definedTableOptions.state,
},
} as MRT_StatefulTableOptions<TData>);
initState.globalFilterFn = definedTableOptions.globalFilterFn ?? 'fuzzy';
return initState;
}, []);
definedTableOptions.initialState = initialState;
const [actionCell, setActionCell] = useState<MRT_Cell<TData> | null>(
initialState.actionCell ?? null,
);
const [creatingRow, _setCreatingRow] = useState<MRT_Row<TData> | null>(
initialState.creatingRow ?? null,
);
const [columnFilterFns, setColumnFilterFns] =
useState<MRT_ColumnFilterFnsState>(() =>
Object.assign(
{},
...getAllLeafColumnDefs(
definedTableOptions.columns as MRT_ColumnDef<TData>[],
).map((col) => ({
[getColumnId(col)]:
col.filterFn instanceof Function
? (col.filterFn.name ?? 'custom')
: (col.filterFn ??
initialState?.columnFilterFns?.[getColumnId(col)] ??
getDefaultColumnFilterFn(col)),
})),
),
);
const [columnOrder, onColumnOrderChange] = useState<MRT_ColumnOrderState>(
initialState.columnOrder ?? [],
);
const [columnSizingInfo, onColumnSizingInfoChange] =
useState<MRT_ColumnSizingInfoState>(
initialState.columnSizingInfo ?? ({} as MRT_ColumnSizingInfoState),
);
const [density, setDensity] = useState<MRT_DensityState>(
initialState?.density ?? 'comfortable',
);
const [draggingColumn, setDraggingColumn] =
useState<MRT_Column<TData> | null>(initialState.draggingColumn ?? null);
const [draggingRow, setDraggingRow] = useState<MRT_Row<TData> | null>(
initialState.draggingRow ?? null,
);
const [editingCell, setEditingCell] = useState<MRT_Cell<TData> | null>(
initialState.editingCell ?? null,
);
const [editingRow, setEditingRow] = useState<MRT_Row<TData> | null>(
initialState.editingRow ?? null,
);
const [globalFilterFn, setGlobalFilterFn] = useState<MRT_FilterOption>(
initialState.globalFilterFn ?? 'fuzzy',
);
const [grouping, onGroupingChange] = useState<MRT_GroupingState>(
initialState.grouping ?? [],
);
const [hoveredColumn, setHoveredColumn] = useState<Partial<
MRT_Column<TData>
> | null>(initialState.hoveredColumn ?? null);
const [hoveredRow, setHoveredRow] = useState<Partial<MRT_Row<TData>> | null>(
initialState.hoveredRow ?? null,
);
const [isFullScreen, setIsFullScreen] = useState<boolean>(
initialState?.isFullScreen ?? false,
);
const [pagination, onPaginationChange] = useState<MRT_PaginationState>(
initialState?.pagination ?? { pageIndex: 0, pageSize: 10 },
);
const [showAlertBanner, setShowAlertBanner] = useState<boolean>(
initialState?.showAlertBanner ?? false,
);
const [showColumnFilters, setShowColumnFilters] = useState<boolean>(
initialState?.showColumnFilters ?? false,
);
const [showGlobalFilter, setShowGlobalFilter] = useState<boolean>(
initialState?.showGlobalFilter ?? false,
);
const [showToolbarDropZone, setShowToolbarDropZone] = useState<boolean>(
initialState?.showToolbarDropZone ?? false,
);
definedTableOptions.state = {
actionCell,
columnFilterFns,
columnOrder,
columnSizingInfo,
creatingRow,
density,
draggingColumn,
draggingRow,
editingCell,
editingRow,
globalFilterFn,
grouping,
hoveredColumn,
hoveredRow,
isFullScreen,
pagination,
showAlertBanner,
showColumnFilters,
showGlobalFilter,
showToolbarDropZone,
...definedTableOptions.state,
};
//The table options now include all state needed to help determine column visibility and order logic
const statefulTableOptions =
definedTableOptions as MRT_StatefulTableOptions<TData>;
//don't recompute columnDefs while resizing column or dragging column/row
const columnDefsRef = useRef<MRT_ColumnDef<TData>[]>([]);
statefulTableOptions.columns =
statefulTableOptions.state.columnSizingInfo.isResizingColumn ||
statefulTableOptions.state.draggingColumn ||
statefulTableOptions.state.draggingRow
? columnDefsRef.current
: prepareColumns({
columnDefs: [
...([
showRowPinningColumn(statefulTableOptions) &&
getMRT_RowPinningColumnDef(statefulTableOptions),
showRowDragColumn(statefulTableOptions) &&
getMRT_RowDragColumnDef(statefulTableOptions),
showRowActionsColumn(statefulTableOptions) &&
getMRT_RowActionsColumnDef(statefulTableOptions),
showRowExpandColumn(statefulTableOptions) &&
getMRT_RowExpandColumnDef(statefulTableOptions),
showRowSelectionColumn(statefulTableOptions) &&
getMRT_RowSelectColumnDef(statefulTableOptions),
showRowNumbersColumn(statefulTableOptions) &&
getMRT_RowNumbersColumnDef(statefulTableOptions),
].filter(Boolean) as MRT_ColumnDef<TData>[]),
...statefulTableOptions.columns,
...([
showRowSpacerColumn(statefulTableOptions) &&
getMRT_RowSpacerColumnDef(statefulTableOptions),
].filter(Boolean) as MRT_ColumnDef<TData>[]),
],
tableOptions: statefulTableOptions,
});
columnDefsRef.current = statefulTableOptions.columns;
//if loading, generate blank rows to show skeleton loaders
statefulTableOptions.data = useMemo(
() =>
(statefulTableOptions.state.isLoading ||
statefulTableOptions.state.showSkeletons) &&
!statefulTableOptions.data.length
? [
...Array(
Math.min(statefulTableOptions.state.pagination.pageSize, 20),
).fill(null),
].map(() =>
Object.assign(
{},
...getAllLeafColumnDefs(statefulTableOptions.columns).map(
(col) => ({
[getColumnId(col)]: null,
}),
),
),
)
: statefulTableOptions.data,
[
statefulTableOptions.data,
statefulTableOptions.state.isLoading,
statefulTableOptions.state.showSkeletons,
],
);
//@ts-expect-error
const table = useReactTable({
onColumnOrderChange,
onColumnSizingInfoChange,
onGroupingChange,
onPaginationChange,
...statefulTableOptions,
globalFilterFn: statefulTableOptions.filterFns?.[globalFilterFn ?? 'fuzzy'],
}) as MRT_TableInstance<TData>;
table.refs = {
actionCellRef,
bottomToolbarRef,
editInputRefs,
filterInputRefs,
lastSelectedRowId,
searchInputRef,
tableContainerRef,
tableFooterRef,
tableHeadCellRefs,
tableHeadRef,
tablePaperRef,
topToolbarRef,
};
table.setActionCell =
statefulTableOptions.onActionCellChange ?? setActionCell;
table.setCreatingRow = (row: MRT_Updater<MRT_Row<TData> | null | true>) => {
let _row = row;
if (row === true) {
_row = createRow(table);
}
statefulTableOptions?.onCreatingRowChange?.(
_row as MRT_Row<TData> | null,
) ?? _setCreatingRow(_row as MRT_Row<TData> | null);
};
table.setColumnFilterFns =
statefulTableOptions.onColumnFilterFnsChange ?? setColumnFilterFns;
table.setDensity = statefulTableOptions.onDensityChange ?? setDensity;
table.setDraggingColumn =
statefulTableOptions.onDraggingColumnChange ?? setDraggingColumn;
table.setDraggingRow =
statefulTableOptions.onDraggingRowChange ?? setDraggingRow;
table.setEditingCell =
statefulTableOptions.onEditingCellChange ?? setEditingCell;
table.setEditingRow =
statefulTableOptions.onEditingRowChange ?? setEditingRow;
table.setGlobalFilterFn =
statefulTableOptions.onGlobalFilterFnChange ?? setGlobalFilterFn;
table.setHoveredColumn =
statefulTableOptions.onHoveredColumnChange ?? setHoveredColumn;
table.setHoveredRow =
statefulTableOptions.onHoveredRowChange ?? setHoveredRow;
table.setIsFullScreen =
statefulTableOptions.onIsFullScreenChange ?? setIsFullScreen;
table.setShowAlertBanner =
statefulTableOptions.onShowAlertBannerChange ?? setShowAlertBanner;
table.setShowColumnFilters =
statefulTableOptions.onShowColumnFiltersChange ?? setShowColumnFilters;
table.setShowGlobalFilter =
statefulTableOptions.onShowGlobalFilterChange ?? setShowGlobalFilter;
table.setShowToolbarDropZone =
statefulTableOptions.onShowToolbarDropZoneChange ?? setShowToolbarDropZone;
useMRT_Effects(table);
return table;
};