From 401d69b110a1ce02b1455871bf4c07b78aada815 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Wed, 16 Oct 2024 20:26:04 -0500 Subject: [PATCH] memo refactor --- examples/react/filters-faceted/src/main.tsx | 16 +- .../table-core/src/core/table/Tables.types.ts | 6 +- .../src/core/table/constructTable.ts | 26 -- .../src/core/table/createCoreRowModel.ts | 2 +- .../createFacetedMinMaxValues.ts | 2 +- .../createFacetedUniqueValues.ts | 63 ++-- .../createFilteredRowModel.ts | 261 ++++++++-------- .../column-grouping/createGroupedRowModel.ts | 291 +++++++++--------- .../row-expanding/createExpandedRowModel.ts | 2 +- .../row-pagination/RowPagination.utils.ts | 4 +- .../row-pagination/createPaginatedRowModel.ts | 2 +- .../row-sorting/createSortedRowModel.ts | 2 +- packages/table-core/src/utils.ts | 137 ++------- 13 files changed, 358 insertions(+), 456 deletions(-) diff --git a/examples/react/filters-faceted/src/main.tsx b/examples/react/filters-faceted/src/main.tsx index a43e68bf59..a93ebbbfc6 100644 --- a/examples/react/filters-faceted/src/main.tsx +++ b/examples/react/filters-faceted/src/main.tsx @@ -16,11 +16,9 @@ import { flexRender, sortingFns, tableFeatures, - processingFns, useTable, } from '@tanstack/react-table' import { makeData } from './makeData' -import type { ProcessingFns } from '../../../../packages/table-core/dist/esm/types/ProcessingFns' import type { CellData, Column, @@ -38,11 +36,6 @@ const _features = tableFeatures({ RowSorting, }) -const _processingFns = processingFns(_features, { - filterFns, - sortingFns, -}) - declare module '@tanstack/react-table' { // allows us to define custom properties for our columns interface ColumnMeta< @@ -109,7 +102,10 @@ function App() { const table = useTable({ _features, - _processingFns, + _processingFns: { + filterFns, + sortingFns, + }, _rowModels: { Filtered: createFilteredRowModel(), // client-side filtering Paginated: createPaginatedRowModel(), @@ -174,7 +170,7 @@ function App() { {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map((cell) => { + {row.getAllCells().map((cell) => { return ( {flexRender( @@ -269,7 +265,7 @@ function App() { ) } -function Filter({ column }: { column: Column }) { +function Filter({ column }: { column: Column }) { const { filterVariant } = column.columnDef.meta ?? {} const columnFilterValue = column.getFilterValue() diff --git a/packages/table-core/src/core/table/Tables.types.ts b/packages/table-core/src/core/table/Tables.types.ts index f7e1faeb81..6b1eb4bac7 100644 --- a/packages/table-core/src/core/table/Tables.types.ts +++ b/packages/table-core/src/core/table/Tables.types.ts @@ -118,6 +118,11 @@ export interface Table_CoreProperties< * @link [Guide](https://tanstack.com/table/v8/docs/guide/tables) */ _processingFns: ProcessingFns + /** + * The row models that are enabled for the table. + * @link [API Docs](https://tanstack.com/table/v8/docs/api/core/table#_rowmodels) + * @link [Guide](https://tanstack.com/table/v8/docs/guide/tables) + */ _rowModels: CachedRowModels /** * This is the resolved initial state of the table. @@ -137,7 +142,6 @@ export interface Table_Table< TFeatures extends TableFeatures, TData extends RowData, > extends Table_CoreProperties { - _queue: (cb: () => void) => void /** * Returns the core row model before any processing has been applied. * @link [API Docs](https://tanstack.com/table/v8/docs/api/core/table#getcorerowmodel) diff --git a/packages/table-core/src/core/table/constructTable.ts b/packages/table-core/src/core/table/constructTable.ts index 28a7c0a020..f8c791c491 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -49,32 +49,6 @@ export function constructTable< Object.assign(table, coreInstance) - const queued: Array<() => void> = [] - let queuedTimeout = false - - table._queue = (cb) => { - queued.push(cb) - - if (!queuedTimeout) { - queuedTimeout = true - - // Schedule a microtask to run the queued callbacks after - // the current call stack (render, etc) has finished. - Promise.resolve() - .then(() => { - while (queued.length) { - queued.shift()!() - } - queuedTimeout = false - }) - .catch((error) => - setTimeout(() => { - throw error - }), - ) - } - } - for (const feature of featuresList) { feature.constructTable?.(table) } diff --git a/packages/table-core/src/core/table/createCoreRowModel.ts b/packages/table-core/src/core/table/createCoreRowModel.ts index 1fdf888066..e2b7028418 100644 --- a/packages/table-core/src/core/table/createCoreRowModel.ts +++ b/packages/table-core/src/core/table/createCoreRowModel.ts @@ -15,7 +15,7 @@ export function createCoreRowModel< return (table: Table) => tableMemo({ debug: isDev && (table.options.debugAll ?? table.options.debugTable), - fnName: 'createCoreRowModel', + fnName: 'table.createCoreRowModel', memoDeps: () => [table.options.data], fn: (data) => _createCoreRowModel(table, data), onAfterUpdate: () => table_autoResetPageIndex(table), diff --git a/packages/table-core/src/features/column-faceting/createFacetedMinMaxValues.ts b/packages/table-core/src/features/column-faceting/createFacetedMinMaxValues.ts index 9196bd8e43..b4bf7466f4 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedMinMaxValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedMinMaxValues.ts @@ -16,7 +16,7 @@ export function createFacetedMinMaxValues< return (table, columnId) => tableMemo({ debug: isDev && (table.options.debugAll ?? table.options.debugTable), - fnName: 'createFacetedMinMaxValues', + fnName: 'table.createFacetedMinMaxValues', memoDeps: () => [ column_getFacetedRowModel(table.getColumn(columnId), table)(), ], diff --git a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts index f3076ae14f..7b97e4e2a6 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts @@ -1,4 +1,4 @@ -import { getMemoOptions, memo } from '../../utils' +import { isDev, tableMemo } from '../../utils' import { row_getUniqueValues } from '../../core/rows/Rows.utils' import { column_getFacetedRowModel } from './ColumnFaceting.utils' import type { RowData } from '../../types/type-utils' @@ -13,34 +13,43 @@ export function createFacetedUniqueValues< columnId: string, ) => () => Map { return (table, columnId) => - memo( - () => [column_getFacetedRowModel(table.getColumn(columnId), table)()], - (facetedRowModel) => { - if (!facetedRowModel) return new Map() + tableMemo({ + debug: isDev && (table.options.debugAll ?? table.options.debugTable), + fnName: 'table.createFacetedUniqueValues', + memoDeps: () => [ + column_getFacetedRowModel(table.getColumn(columnId), table)(), + ], + fn: (facetedRowModel) => + _createFacetedUniqueValues(table, columnId, facetedRowModel), + }) +} + +function _createFacetedUniqueValues< + TFeatures extends TableFeatures, + TData extends RowData, +>( + table: Table, + columnId: string, + facetedRowModel?: ReturnType>, +): Map { + if (!facetedRowModel) return new Map() - const facetedUniqueValues = new Map() + const facetedUniqueValues = new Map() - for (const row of facetedRowModel.flatRows) { - const values = row_getUniqueValues(row, table, columnId) + for (const row of facetedRowModel.flatRows) { + const values = row_getUniqueValues(row, table, columnId) - for (const value of values) { - if (facetedUniqueValues.has(value)) { - facetedUniqueValues.set( - value, - (facetedUniqueValues.get(value) ?? 0) + 1, - ) - } else { - facetedUniqueValues.set(value, 1) - } - } - } + for (const value of values) { + if (facetedUniqueValues.has(value)) { + facetedUniqueValues.set( + value, + (facetedUniqueValues.get(value) ?? 0) + 1, + ) + } else { + facetedUniqueValues.set(value, 1) + } + } + } - return facetedUniqueValues - }, - getMemoOptions( - table.options, - 'debugTable', - `getFacetedUniqueValues_${columnId}`, - ), - ) + return facetedUniqueValues } diff --git a/packages/table-core/src/features/column-filtering/createFilteredRowModel.ts b/packages/table-core/src/features/column-filtering/createFilteredRowModel.ts index f6669cf395..d8fcd897fe 100644 --- a/packages/table-core/src/features/column-filtering/createFilteredRowModel.ts +++ b/packages/table-core/src/features/column-filtering/createFilteredRowModel.ts @@ -1,4 +1,4 @@ -import { getMemoOptions, memo } from '../../utils' +import { isDev, tableMemo } from '../../utils' import { table_getColumn } from '../../core/columns/Columns.utils' import { column_getCanGlobalFilter, @@ -26,141 +26,142 @@ export function createFilteredRowModel< TData extends RowData, >(): (table: Table) => () => RowModel { return (table) => - memo( - () => [ + tableMemo({ + debug: isDev && (table.options.debugAll ?? table.options.debugTable), + fnName: 'table.createFilteredRowModel', + memoDeps: () => [ table_getPreFilteredRowModel(table), table_getState(table).columnFilters, table_getState(table).globalFilter, ], - (rowModel, columnFilters, globalFilter) => { - if (!rowModel.rows.length || (!columnFilters.length && !globalFilter)) { - for (const row of rowModel.flatRows) { - row.columnFilters = {} - row.columnFiltersMeta = {} - } - return rowModel - } - - const resolvedColumnFilters: Array< - ResolvedColumnFilter - > = [] - const resolvedGlobalFilters: Array< - ResolvedColumnFilter - > = [] - - columnFilters?.forEach((d) => { - const column = table_getColumn(table, d.id) - - if (!column) { - return - } - - const filterFn = column_getFilterFn(column, table) - - if (!filterFn) { - if (process.env.NODE_ENV !== 'production') { - console.warn( - `Could not find a valid 'column.filterFn' for column with the ID: ${column.id}.`, - ) - } - return - } - - resolvedColumnFilters.push({ - id: d.id, - filterFn, - resolvedValue: filterFn.resolveFilterValue?.(d.value) ?? d.value, - }) - }) - - const filterableIds = columnFilters?.map((d) => d.id) ?? [] - - const globalFilterFn = table_getGlobalFilterFn(table) - - const globallyFilterableColumns = table - .getAllLeafColumns() - .filter((column) => column_getCanGlobalFilter(column, table)) + fn: () => _createFilteredRowModel(table), + onAfterUpdate: () => table_autoResetPageIndex(table), + }) +} +function _createFilteredRowModel< + TFeatures extends TableFeatures, + TData extends RowData, +>(table: Table): RowModel { + const rowModel = table_getPreFilteredRowModel(table) + const { columnFilters, globalFilter } = table_getState(table) + + if (!rowModel.rows.length || (!columnFilters.length && !globalFilter)) { + for (const row of rowModel.flatRows) { + row.columnFilters = {} + row.columnFiltersMeta = {} + } + return rowModel + } + + const resolvedColumnFilters: Array> = + [] + const resolvedGlobalFilters: Array> = + [] + + columnFilters?.forEach((d) => { + const column = table_getColumn(table, d.id) + + if (!column) { + return + } + + const filterFn = column_getFilterFn(column, table) + + if (!filterFn) { + if (process.env.NODE_ENV !== 'production') { + console.warn( + `Could not find a valid 'column.filterFn' for column with the ID: ${column.id}.`, + ) + } + return + } + + resolvedColumnFilters.push({ + id: d.id, + filterFn, + resolvedValue: filterFn.resolveFilterValue?.(d.value) ?? d.value, + }) + }) + + const filterableIds = columnFilters?.map((d) => d.id) ?? [] + + const globalFilterFn = table_getGlobalFilterFn(table) + + const globallyFilterableColumns = table + .getAllLeafColumns() + .filter((column) => column_getCanGlobalFilter(column, table)) + + if (globalFilter && globalFilterFn && globallyFilterableColumns.length) { + filterableIds.push('__global__') + + globallyFilterableColumns.forEach((column) => { + resolvedGlobalFilters.push({ + id: column.id, + filterFn: globalFilterFn, + resolvedValue: + globalFilterFn.resolveFilterValue?.(globalFilter) ?? globalFilter, + }) + }) + } + + // Flag the prefiltered row model with each filter state + for (const row of rowModel.flatRows) { + row.columnFilters = {} + + if (resolvedColumnFilters.length) { + for (const currentColumnFilter of resolvedColumnFilters) { + const id = currentColumnFilter.id + + // Tag the row with the column filter state + row.columnFilters[id] = currentColumnFilter.filterFn( + row, + id, + currentColumnFilter.resolvedValue, + (filterMeta) => { + row.columnFiltersMeta[id] = filterMeta + }, + ) + } + } + + if (resolvedGlobalFilters.length) { + for (const currentGlobalFilter of resolvedGlobalFilters) { + const id = currentGlobalFilter.id + // Tag the row with the first truthy global filter state if ( - globalFilter && - globalFilterFn && - globallyFilterableColumns.length + currentGlobalFilter.filterFn( + row, + id, + currentGlobalFilter.resolvedValue, + (filterMeta) => { + row.columnFiltersMeta[id] = filterMeta + }, + ) ) { - filterableIds.push('__global__') - - globallyFilterableColumns.forEach((column) => { - resolvedGlobalFilters.push({ - id: column.id, - filterFn: globalFilterFn, - resolvedValue: - globalFilterFn.resolveFilterValue?.(globalFilter) ?? - globalFilter, - }) - }) + row.columnFilters.__global__ = true + break } - - // Flag the prefiltered row model with each filter state - for (const row of rowModel.flatRows) { - row.columnFilters = {} - - if (resolvedColumnFilters.length) { - for (const currentColumnFilter of resolvedColumnFilters) { - const id = currentColumnFilter.id - - // Tag the row with the column filter state - row.columnFilters[id] = currentColumnFilter.filterFn( - row, - id, - currentColumnFilter.resolvedValue, - (filterMeta) => { - row.columnFiltersMeta[id] = filterMeta - }, - ) - } - } - - if (resolvedGlobalFilters.length) { - for (const currentGlobalFilter of resolvedGlobalFilters) { - const id = currentGlobalFilter.id - // Tag the row with the first truthy global filter state - if ( - currentGlobalFilter.filterFn( - row, - id, - currentGlobalFilter.resolvedValue, - (filterMeta) => { - row.columnFiltersMeta[id] = filterMeta - }, - ) - ) { - row.columnFilters.__global__ = true - break - } - } - - if (row.columnFilters.__global__ !== true) { - row.columnFilters.__global__ = false - } - } - } - - const filterRowsImpl = ( - row: Row & Row_ColumnFiltering, - ) => { - // Horizontally filter rows through each column - for (const columnId of filterableIds) { - if (row.columnFilters[columnId] === false) { - return false - } - } - return true - } - - // Filter final rows using all of the active filters - return filterRows(rowModel.rows as any, filterRowsImpl as any, table) - }, - getMemoOptions(table.options, 'debugTable', 'getFilteredRowModel', () => - table_autoResetPageIndex(table), - ), - ) + } + + if (row.columnFilters.__global__ !== true) { + row.columnFilters.__global__ = false + } + } + } + + const filterRowsImpl = ( + row: Row & Row_ColumnFiltering, + ) => { + // Horizontally filter rows through each column + for (const columnId of filterableIds) { + if (row.columnFilters[columnId] === false) { + return false + } + } + return true + } + + // Filter final rows using all of the active filters + return filterRows(rowModel.rows as any, filterRowsImpl as any, table) } diff --git a/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts b/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts index f80e12055d..4f0b30a99c 100644 --- a/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts +++ b/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts @@ -1,5 +1,5 @@ +import { flattenBy, isDev, tableMemo } from '../../utils' import { constructRow } from '../../core/rows/constructRow' -import { flattenBy, getMemoOptions, memo } from '../../utils' import { table_getColumn } from '../../core/columns/Columns.utils' import { table_getState } from '../../core/table/Tables.utils' import { table_autoResetExpanded } from '../row-expanding/RowExpanding.utils' @@ -8,7 +8,6 @@ import { row_getGroupingValue, table_getPreGroupedRowModel, } from './ColumnGrouping.utils' - import type { RowData } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' import type { RowModel } from '../../types/RowModel' @@ -20,174 +19,164 @@ export function createGroupedRowModel< TData extends RowData, >(): (table: Table) => () => RowModel { return (table) => - memo( - () => [ + tableMemo({ + debug: isDev && (table.options.debugAll ?? table.options.debugTable), + fnName: 'table.createGroupedRowModel', + memoDeps: () => [ table_getState(table).grouping, table_getPreGroupedRowModel(table), ], - (grouping, rowModel) => { - if (!rowModel.rows.length || !grouping?.length) { - rowModel.rows.forEach((row) => { - row.depth = 0 - row.parentId = undefined - }) - return rowModel + fn: () => _createGroupedRowModel(table), + onAfterUpdate: () => { + table_autoResetExpanded(table) + table_autoResetPageIndex(table) + }, + }) +} + +function _createGroupedRowModel< + TFeatures extends TableFeatures, + TData extends RowData, +>(table: Table): RowModel { + const rowModel = table_getPreGroupedRowModel(table) + const grouping = table_getState(table).grouping + + if (!rowModel.rows.length || !grouping?.length) { + rowModel.rows.forEach((row) => { + row.depth = 0 + row.parentId = undefined + }) + return rowModel + } + + // Filter the grouping list down to columns that exist + const existingGrouping = grouping.filter((columnId) => + table_getColumn(table, columnId), + ) + + const groupedFlatRows: Array> = [] + const groupedRowsById: Record> = {} + + // Recursively group the data + const groupUpRecursively = ( + rows: Array>, + depth = 0, + parentId?: string, + ) => { + // Grouping depth has been been met + // Stop grouping and simply rewrite thd depth and row relationships + if (depth >= existingGrouping.length) { + return rows.map((row) => { + row.depth = depth + + groupedFlatRows.push(row) + groupedRowsById[row.id] = row + + if (row.subRows) { + row.subRows = groupUpRecursively(row.subRows, depth + 1, row.id) } - // Filter the grouping list down to columns that exist - const existingGrouping = grouping.filter((columnId) => - table_getColumn(table, columnId), + return row + }) + } + + const columnId: string = existingGrouping[depth]! + + // Group the rows together for this level + const rowGroupsMap = groupBy(rows, columnId, table) + + // Perform aggregations for each group + const aggregatedGroupedRows = Array.from(rowGroupsMap.entries()).map( + ([groupingValue, groupedRows], index) => { + let id = `${columnId}:${groupingValue}` + id = parentId ? `${parentId}>${id}` : id + + // First, Recurse to group sub rows before aggregation + const subRows = groupUpRecursively(groupedRows, depth + 1, id) + + subRows.forEach((subRow) => { + subRow.parentId = id + }) + + // Flatten the leaf rows of the rows in this group + const leafRows = depth + ? flattenBy(groupedRows, (row) => row.subRows) + : groupedRows + + const row = constructRow( + table, + id, + leafRows[0]!.original, + index, + depth, + undefined, + parentId, ) - const groupedFlatRows: Array> = [] - const groupedRowsById: Record> = {} - // const onlyGroupedFlatRows: Row[] = []; - // const onlyGroupedRowsById: Record = {}; - // const nonGroupedFlatRows: Row[] = []; - // const nonGroupedRowsById: Record = {}; - - // Recursively group the data - const groupUpRecursively = ( - rows: Array>, - depth = 0, - parentId?: string, - ) => { - // Grouping depth has been been met - // Stop grouping and simply rewrite thd depth and row relationships - if (depth >= existingGrouping.length) { - return rows.map((row) => { - row.depth = depth - - groupedFlatRows.push(row) - groupedRowsById[row.id] = row - - if (row.subRows) { - row.subRows = groupUpRecursively(row.subRows, depth + 1, row.id) + Object.assign(row, { + groupingColumnId: columnId, + groupingValue, + subRows, + leafRows, + getValue: (colId: string) => { + // Don't aggregate columns that are in the grouping + if (existingGrouping.includes(colId)) { + if (row._valuesCache.hasOwnProperty(colId)) { + return row._valuesCache[colId] } - return row - }) - } - - const columnId: string = existingGrouping[depth]! - - // Group the rows together for this level - const rowGroupsMap = groupBy(rows, columnId, table) - - // Perform aggregations for each group - const aggregatedGroupedRows = Array.from(rowGroupsMap.entries()).map( - ([groupingValue, groupedRows], index) => { - let id = `${columnId}:${groupingValue}` - id = parentId ? `${parentId}>${id}` : id - - // First, Recurse to group sub rows before aggregation - const subRows = groupUpRecursively(groupedRows, depth + 1, id) - - subRows.forEach((subRow) => { - subRow.parentId = id - }) - - // Flatten the leaf rows of the rows in this group - const leafRows = depth - ? flattenBy(groupedRows, (row) => row.subRows) - : groupedRows - - const row = constructRow( - table, - id, - leafRows[0]!.original, - index, - depth, - undefined, - parentId, - ) + if (groupedRows[0]) { + row._valuesCache[colId] = + groupedRows[0].getValue(colId) ?? undefined + } + + return row._valuesCache[colId] + } + + if (row._groupingValuesCache.hasOwnProperty(colId)) { + return row._groupingValuesCache[colId] + } - Object.assign(row, { - groupingColumnId: columnId, - groupingValue, - subRows, + // Aggregate the values + const column = table.getColumn(colId) + const aggregateFn = column.getAggregationFn() + + if (aggregateFn) { + row._groupingValuesCache[colId] = aggregateFn( + colId, leafRows, - getValue: (colId: string) => { - // Don't aggregate columns that are in the grouping - if (existingGrouping.includes(colId)) { - if (row._valuesCache.hasOwnProperty(colId)) { - return row._valuesCache[colId] - } - - if (groupedRows[0]) { - row._valuesCache[colId] = - groupedRows[0].getValue(colId) ?? undefined - } - - return row._valuesCache[colId] - } - - if (row._groupingValuesCache.hasOwnProperty(colId)) { - return row._groupingValuesCache[colId] - } - - // Aggregate the values - const column = table.getColumn(colId) - const aggregateFn = column.getAggregationFn() - - if (aggregateFn) { - row._groupingValuesCache[colId] = aggregateFn( - colId, - leafRows, - groupedRows, - ) - - return row._groupingValuesCache[colId] - } - }, - }) - - subRows.forEach((subRow) => { - groupedFlatRows.push(subRow) - groupedRowsById[subRow.id] = subRow - // if (subRow.getIsGrouped?.()) { - // onlyGroupedFlatRows.push(subRow); - // onlyGroupedRowsById[subRow.id] = subRow; - // } else { - // nonGroupedFlatRows.push(subRow); - // nonGroupedRowsById[subRow.id] = subRow; - // } - }) - - return row - }, - ) - - return aggregatedGroupedRows - } + groupedRows, + ) - const groupedRows = groupUpRecursively(rowModel.rows, 0) + return row._groupingValuesCache[colId] + } + }, + }) - groupedRows.forEach((subRow) => { + subRows.forEach((subRow) => { groupedFlatRows.push(subRow) groupedRowsById[subRow.id] = subRow - // if (subRow.getIsGrouped?.()) { - // onlyGroupedFlatRows.push(subRow); - // onlyGroupedRowsById[subRow.id] = subRow; - // } else { - // nonGroupedFlatRows.push(subRow); - // nonGroupedRowsById[subRow.id] = subRow; - // } }) - return { - rows: groupedRows, - flatRows: groupedFlatRows, - rowsById: groupedRowsById, - } + return row }, - getMemoOptions(table.options, 'debugTable', 'getGroupedRowModel', () => { - table._queue(() => { - table_autoResetExpanded(table) - table_autoResetPageIndex(table) - }) - }), ) + + return aggregatedGroupedRows + } + + const groupedRows = groupUpRecursively(rowModel.rows, 0) + + groupedRows.forEach((subRow) => { + groupedFlatRows.push(subRow) + groupedRowsById[subRow.id] = subRow + }) + + return { + rows: groupedRows, + flatRows: groupedFlatRows, + rowsById: groupedRowsById, + } } function groupBy( diff --git a/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts b/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts index 590ad78c75..ec5e648166 100644 --- a/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts +++ b/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts @@ -19,7 +19,7 @@ export function createExpandedRowModel< return (table) => tableMemo({ debug: isDev && (table.options.debugAll ?? table.options.debugTable), - fnName: 'createExpandedRowModel', + fnName: 'table.createExpandedRowModel', memoDeps: () => [ table_getState(table).expanded, table_getPreExpandedRowModel(table), diff --git a/packages/table-core/src/features/row-pagination/RowPagination.utils.ts b/packages/table-core/src/features/row-pagination/RowPagination.utils.ts index fac9bdc9fc..9082f3102f 100644 --- a/packages/table-core/src/features/row-pagination/RowPagination.utils.ts +++ b/packages/table-core/src/features/row-pagination/RowPagination.utils.ts @@ -40,9 +40,7 @@ export function table_autoResetPageIndex< table.options.autoResetPageIndex ?? !table.options.manualPagination ) { - queueMicrotask(() => { - table_resetPageIndex(table) - }) + table_resetPageIndex(table) } } diff --git a/packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts b/packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts index 5f27deb54d..f043bd0543 100644 --- a/packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts +++ b/packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts @@ -20,7 +20,7 @@ export function createPaginatedRowModel< return (table) => tableMemo({ debug: isDev && (table.options.debugAll ?? table.options.debugTable), - fnName: 'createPaginatedRowModel', + fnName: 'table.createPaginatedRowModel', memoDeps: () => [ table_getPrePaginationRowModel(table), table_getState(table).pagination, diff --git a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts index 9003797bcf..b06b9d302e 100644 --- a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts +++ b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts @@ -25,7 +25,7 @@ export function createSortedRowModel< return (table) => tableMemo({ debug: isDev && (table.options.debugAll ?? table.options.debugTable), - fnName: 'createSortedRowModel', + fnName: 'table.createSortedRowModel', memoDeps: () => [ table_getState(table).sorting, table_getPreSortedRowModel(table), diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index ea3e994dd9..62afecf1bc 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -1,7 +1,6 @@ import type { Table } from './types/Table' import type { NoInfer, RowData, Updater } from './types/type-utils' import type { TableFeatures } from './types/TableFeatures' -import type { TableOptions } from './types/TableOptions' import type { TableState } from './types/TableState' export const isDev = process.env.NODE_ENV === 'development' @@ -59,117 +58,37 @@ export function flattenBy( return flat } -// TODO delete old memo function -export function memo, TDepArgs, TResult>( - memoDeps: (depArgs?: TDepArgs) => [...TDeps], - fn: (...args: NoInfer<[...TDeps]>) => TResult, - opts: { - key: any - debug?: () => any - onChange?: (result: TResult) => void - }, -): (depArgs?: TDepArgs) => TResult { - let deps: Array = [] - let result: TResult | undefined - - return (depArgs) => { - let depTime: number - if (opts.key && opts.debug) depTime = Date.now() - - const newDeps = memoDeps(depArgs) - - const depsChanged = - newDeps.length !== deps.length || - newDeps.some((dep: any, index: number) => deps[index] !== dep) - - if (!depsChanged) { - return result! - } - - deps = newDeps - - let resultTime: number - if (opts.key && opts.debug) resultTime = Date.now() - - result = fn(...newDeps) - opts.onChange?.(result) - - if (opts.key && opts.debug) { - if (opts.debug()) { - const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100 - const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100 - const resultFpsPercentage = resultEndTime / 16 - - const pad = (str: number | string, num: number) => { - str = String(str) - while (str.length < num) { - str = ' ' + str - } - return str - } - - console.info( - `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`, - ` - font-size: .6rem; - font-weight: bold; - color: hsl(${Math.max( - 0, - Math.min(120 - 120 * resultFpsPercentage, 120), - )}deg 100% 31%);`, - opts.key, - ) - } - } - - return result - } -} - -// TODO delete -export function getMemoOptions< - TFeatures extends TableFeatures, - TData extends RowData, ->( - tableOptions: TableOptions, - debugLevel: - | 'debugAll' - | 'debugCells' - | 'debugTable' - | 'debugColumns' - | 'debugRows' - | 'debugHeaders', - key: string, - onChange?: (result: any) => void, -) { - return { - debug: () => tableOptions.debugAll ?? tableOptions[debugLevel], - key: process.env.NODE_ENV === 'development' && key, - onChange, - } -} - interface MemoOptions, TDepArgs, TResult> { memoDeps?: (depArgs?: TDepArgs) => [...TDeps] | undefined fn: (...args: NoInfer) => TResult onAfterUpdate?: (result: TResult) => void onBeforeUpdate?: () => void + onBeforeCompare?: () => void + onAfterCompare?: () => void } -export const _memo = , TDepArgs, TResult>( +export const memo = , TDepArgs, TResult>( options: MemoOptions, ): ((depArgs?: TDepArgs) => TResult) => { - const { memoDeps, fn, onAfterUpdate, onBeforeUpdate } = options + const { + memoDeps, + fn, + onAfterUpdate, + onBeforeUpdate, + onBeforeCompare, + onAfterCompare, + } = options let deps: Array | undefined = [] let result: TResult | undefined return (depArgs): TResult => { + onBeforeCompare?.() const newDeps = memoDeps?.(depArgs) - const depsChanged = !newDeps || newDeps.length !== deps?.length || newDeps.some((dep: any, index: number) => deps?.[index] !== dep) + onAfterCompare?.() if (!depsChanged) { return result! @@ -205,32 +124,44 @@ export function tableMemo, TDepArgs, TResult>( ) { const { debug, fnName, onAfterUpdate, ...memoOptions } = tableMemoOptions - let startTime: number - let endTime: number + let beforeCompareTime: number + let afterCompareTime: number + + let startCalcTime: number + let endCalcTime: number const debugOptions = isDev ? { + onBeforeCompare: () => { + if (debug) beforeCompareTime = performance.now() + }, + onAfterCompare: () => { + if (debug) afterCompareTime = performance.now() + }, onBeforeUpdate: () => { - if (debug) startTime = performance.now() + if (debug) startCalcTime = performance.now() }, onAfterUpdate: () => { if (debug) { - endTime = performance.now() + endCalcTime = performance.now() + const compareTime = + Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 const executionTime = - Math.round((endTime - startTime) * 1000) / 1000 + Math.round((endCalcTime - startCalcTime) * 100) / 100 + const totalTime = compareTime + executionTime console.info( - `%c⏱ ${pad(executionTime, 5)} ms`, + `%c⏱ ${pad(`${compareTime.toFixed(1)} ms + ${executionTime.toFixed(1)} ms`, 17)}`, `font-size: .6rem; font-weight: bold; color: hsl( - ${Math.max(0, Math.min(120 - executionTime, 120))}deg 100% 31%);`, + ${Math.max(0, Math.min(120 - totalTime, 120))}deg 100% 31%);`, fnName, ) } - onAfterUpdate?.() + queueMicrotask(() => onAfterUpdate) }, } : {} - return _memo({ + return memo({ ...memoOptions, ...debugOptions, })