diff --git a/eslint.config.js b/eslint.config.js index fca062d537..951ea2fd77 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,7 +4,6 @@ import vitest from '@vitest/eslint-plugin'; import { defineConfig, globalIgnores } from 'eslint/config'; import jestDom from 'eslint-plugin-jest-dom'; import react from 'eslint-plugin-react'; -import reactCompiler from 'eslint-plugin-react-compiler'; import reactHooks from 'eslint-plugin-react-hooks'; import reactHooksExtra from 'eslint-plugin-react-hooks-extra'; import sonarjs from 'eslint-plugin-sonarjs'; @@ -26,7 +25,6 @@ export default defineConfig([ plugins: { react, - 'react-compiler': reactCompiler, 'react-hooks': reactHooks, 'react-hooks-extra': reactHooksExtra, sonarjs, @@ -381,14 +379,11 @@ export default defineConfig([ 'react/style-prop-object': 0, 'react/void-dom-elements-no-children': 1, - // React Compiler - // https://react.dev/learn/react-compiler#installing-eslint-plugin-react-compiler - 'react-compiler/react-compiler': 1, - // React Hooks // https://www.npmjs.com/package/eslint-plugin-react-hooks - 'react-hooks/rules-of-hooks': 1, 'react-hooks/exhaustive-deps': 1, + 'react-hooks/react-compiler': 1, + 'react-hooks/rules-of-hooks': 1, // React Hooks Extra // https://eslint-react.xyz/ diff --git a/package.json b/package.json index 4ba56b6cd8..9f51335400 100644 --- a/package.json +++ b/package.json @@ -73,12 +73,12 @@ "@vitest/eslint-plugin": "^1.1.43", "@wyw-in-js/rollup": "^0.6.0", "@wyw-in-js/vite": "^0.6.0", + "babel-plugin-react-compiler": "19.1.0-rc.1", "browserslist": "^4.24.4", "eslint": "^9.23.0", "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-compiler": "^19.0.0-beta-e993439-20250328", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-hooks": "^6.0.0-rc.1", "eslint-plugin-react-hooks-extra": "^1.40.1", "eslint-plugin-sonarjs": "^3.0.2", "eslint-plugin-testing-library": "^7.1.1", diff --git a/rolldown.config.js b/rolldown.config.js index f35de1e7e5..9a7eebfc83 100644 --- a/rolldown.config.js +++ b/rolldown.config.js @@ -3,6 +3,7 @@ import { isAbsolute } from 'node:path'; import wyw from '@wyw-in-js/rollup'; import pkg from './package.json' with { type: 'json' }; import { defineConfig } from 'rolldown'; +import react from '@vitejs/plugin-react'; export default defineConfig({ input: './src/index.ts', @@ -15,6 +16,11 @@ export default defineConfig({ platform: 'browser', external: (id) => !id.startsWith('.') && !isAbsolute(id), plugins: [ + react({ + babel: { + plugins: [['babel-plugin-react-compiler', { target: '19' }]] + } + }), // @ts-expect-error wyw({ preprocessor: 'none', diff --git a/src/Cell.tsx b/src/Cell.tsx index 128cdd113e..e5cbeb7f17 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -1,4 +1,3 @@ -import { memo } from 'react'; import { css } from '@linaria/core'; import { useRovingTabIndex } from './hooks'; @@ -108,10 +107,8 @@ function Cell({ ); } -const CellComponent = memo(Cell) as (props: CellRendererProps) => React.JSX.Element; - -export default CellComponent; +export default Cell; export function defaultRenderCell(key: React.Key, props: CellRendererProps) { - return ; + return ; } diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index c5e667b454..10125b005f 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -1,25 +1,18 @@ -import { - useCallback, - useImperativeHandle, - useLayoutEffect, - useMemo, - useRef, - useState -} from 'react'; +import { useCallback, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react'; import type { Key, KeyboardEvent } from 'react'; import { flushSync } from 'react-dom'; import clsx from 'clsx'; import { + getCalculatedColumns, + getViewportColumns, + getViewportRows, HeaderRowSelectionChangeContext, HeaderRowSelectionContext, RowSelectionChangeContext, - useCalculatedColumns, useColumnWidths, useGridDimensions, useLatestFunc, - useViewportColumns, - useViewportRows, type HeaderRowSelectionContextValue } from './hooks'; import { @@ -348,12 +341,9 @@ export function DataGrid(props: DataGridPr } : setColumnWidthsInternal; - const getColumnWidth = useCallback( - (column: CalculatedColumn) => { - return columnWidths.get(column.key)?.width ?? column.width; - }, - [columnWidths] - ); + function getColumnWidth(column: CalculatedColumn) { + return columnWidths.get(column.key)?.width ?? column.width; + } const [gridRef, gridWidth, gridHeight, horizontalScrollbarHeight] = useGridDimensions(); const { @@ -366,7 +356,7 @@ export function DataGrid(props: DataGridPr templateColumns, layoutCssVars, totalFrozenColumnWidth - } = useCalculatedColumns({ + } = getCalculatedColumns({ rawColumns, defaultColumnOptions, getColumnWidth, @@ -405,16 +395,15 @@ export function DataGrid(props: DataGridPr const { leftKey, rightKey } = getLeftRightKey(direction); const ariaRowCount = rawAriaRowCount ?? headerRowsCount + rows.length + summaryRowsCount; - const defaultGridComponents = useMemo( - () => ({ - renderCheckbox, - renderSortStatus, - renderCell - }), - [renderCheckbox, renderSortStatus, renderCell] - ); + const defaultGridComponents = { + renderCheckbox, + renderSortStatus, + renderCell + }; + + const headerSelectionValue = getHeaderSelectionValue(); - const headerSelectionValue = useMemo((): HeaderRowSelectionContextValue => { + function getHeaderSelectionValue(): HeaderRowSelectionContextValue { // no rows to select = explicitely unchecked let hasSelectedRow = false; let hasUnselectedRow = false; @@ -435,7 +424,7 @@ export function DataGrid(props: DataGridPr isRowSelected: hasSelectedRow && !hasUnselectedRow, isIndeterminate: hasSelectedRow && hasUnselectedRow }; - }, [rows, selectedRows, rowKeyGetter]); + } const { rowOverscanStartIdx, @@ -445,7 +434,7 @@ export function DataGrid(props: DataGridPr getRowTop, getRowHeight, findRowIdx - } = useViewportRows({ + } = getViewportRows({ rows, rowHeight, clientHeight, @@ -453,7 +442,7 @@ export function DataGrid(props: DataGridPr enableVirtualization }); - const viewportColumns = useViewportColumns({ + const viewportColumns = getViewportColumns({ columns, colSpanColumns, colOverscanStartIdx, @@ -506,10 +495,10 @@ export function DataGrid(props: DataGridPr /** * callbacks */ - const setDraggedOverRowIdx = useCallback((rowIdx?: number) => { + function setDraggedOverRowIdx(rowIdx?: number) { setOverRowIdx(rowIdx); latestDraggedOverRowIdx.current = rowIdx; - }, []); + } const focusCellOrCellContent = useCallback(() => { const cell = getCellToScroll(gridRef.current!); @@ -999,7 +988,7 @@ export function DataGrid(props: DataGridPr return viewportColumns; } - function getViewportRows() { + function renderViewportRows() { const rowElements: React.ReactNode[] = []; const { idx: selectedIdx, rowIdx: selectedRowIdx } = selectedPosition; @@ -1204,7 +1193,7 @@ export function DataGrid(props: DataGridPr ); })} - {getViewportRows()} + {renderViewportRows()} {bottomSummaryRows?.map((row, rowIdx) => { const gridRowStart = headerAndTopSummaryRowsCount + rows.length + rowIdx + 1; diff --git a/src/GroupCell.tsx b/src/GroupCell.tsx index 935501b46f..2428226cf1 100644 --- a/src/GroupCell.tsx +++ b/src/GroupCell.tsx @@ -1,5 +1,3 @@ -import { memo } from 'react'; - import { useRovingTabIndex } from './hooks'; import { getCellClassname, getCellStyle } from './utils'; import type { CalculatedColumn, GroupRow } from './types'; @@ -17,7 +15,7 @@ interface GroupCellProps { isGroupByColumn: boolean; } -function GroupCell({ +export default function GroupCell({ id, groupKey, childRows, @@ -66,5 +64,3 @@ function GroupCell({ ); } - -export default memo(GroupCell) as (props: GroupCellProps) => React.JSX.Element; diff --git a/src/GroupRow.tsx b/src/GroupRow.tsx index 9fb0fd7443..f3d42a1da9 100644 --- a/src/GroupRow.tsx +++ b/src/GroupRow.tsx @@ -1,4 +1,3 @@ -import { memo, useMemo } from 'react'; import { css } from '@linaria/core'; import clsx from 'clsx'; @@ -31,7 +30,7 @@ interface GroupRowRendererProps extends BaseRenderRowProps { toggleGroup: (expandedGroupId: unknown) => void; } -function GroupedRow({ +export default function GroupedRow({ className, row, rowIdx, @@ -52,10 +51,7 @@ function GroupedRow({ selectCell({ rowIdx, idx: -1 }); } - const selectionValue = useMemo( - (): RowSelectionContextValue => ({ isRowSelectionDisabled: false, isRowSelected }), - [isRowSelected] - ); + const selectionValue: RowSelectionContextValue = { isRowSelectionDisabled: false, isRowSelected }; return ( @@ -95,7 +91,3 @@ function GroupedRow({ ); } - -export default memo(GroupedRow) as ( - props: GroupRowRendererProps -) => React.JSX.Element; diff --git a/src/GroupedColumnHeaderRow.tsx b/src/GroupedColumnHeaderRow.tsx index fc0ae064b5..29d210c18b 100644 --- a/src/GroupedColumnHeaderRow.tsx +++ b/src/GroupedColumnHeaderRow.tsx @@ -1,5 +1,3 @@ -import { memo } from 'react'; - import type { CalculatedColumn, CalculatedColumnParent, Position } from './types'; import GroupedColumnHeaderCell from './GroupedColumnHeaderCell'; import { headerRowClassname } from './HeaderRow'; @@ -12,7 +10,7 @@ export interface GroupedColumnHeaderRowProps { selectedCellIdx: number | undefined; } -function GroupedColumnHeaderRow({ +export default function GroupedColumnHeaderRow({ rowIdx, level, columns, @@ -57,7 +55,3 @@ function GroupedColumnHeaderRow({ ); } - -export default memo(GroupedColumnHeaderRow) as ( - props: GroupedColumnHeaderRowProps -) => React.JSX.Element; diff --git a/src/HeaderRow.tsx b/src/HeaderRow.tsx index 9ece21827f..33961c8ea9 100644 --- a/src/HeaderRow.tsx +++ b/src/HeaderRow.tsx @@ -1,4 +1,4 @@ -import { memo, useId } from 'react'; +import { useId } from 'react'; import { css } from '@linaria/core'; import clsx from 'clsx'; @@ -47,7 +47,7 @@ const headerRow = css` export const headerRowClassname = `rdg-header-row ${headerRow}`; -function HeaderRow({ +export default function HeaderRow({ headerRowClass, rowIdx, columns, @@ -108,7 +108,3 @@ function HeaderRow({ ); } - -export default memo(HeaderRow) as ( - props: HeaderRowProps -) => React.JSX.Element; diff --git a/src/Row.tsx b/src/Row.tsx index 6ee25eeaf4..0e5b52f6bf 100644 --- a/src/Row.tsx +++ b/src/Row.tsx @@ -1,4 +1,3 @@ -import { memo, useMemo } from 'react'; import clsx from 'clsx'; import { RowSelectionContext, useLatestFunc, type RowSelectionContextValue } from './hooks'; @@ -83,10 +82,7 @@ function Row({ } } - const selectionValue = useMemo( - (): RowSelectionContextValue => ({ isRowSelected, isRowSelectionDisabled }), - [isRowSelectionDisabled, isRowSelected] - ); + const selectionValue: RowSelectionContextValue = { isRowSelected, isRowSelectionDisabled }; return ( @@ -103,10 +99,8 @@ function Row({ ); } -const RowComponent = memo(Row) as (props: RenderRowProps) => React.JSX.Element; - -export default RowComponent; +export default Row; export function defaultRenderRow(key: React.Key, props: RenderRowProps) { - return ; + return ; } diff --git a/src/SummaryCell.tsx b/src/SummaryCell.tsx index 4c58097396..732972a4be 100644 --- a/src/SummaryCell.tsx +++ b/src/SummaryCell.tsx @@ -1,4 +1,3 @@ -import { memo } from 'react'; import { css } from '@linaria/core'; import { useRovingTabIndex } from './hooks'; @@ -21,7 +20,7 @@ interface SummaryCellProps extends SharedCellRendererProps { row: SR; } -function SummaryCell({ +export default function SummaryCell({ column, colSpan, row, @@ -57,5 +56,3 @@ function SummaryCell({ ); } - -export default memo(SummaryCell) as (props: SummaryCellProps) => React.JSX.Element; diff --git a/src/SummaryRow.tsx b/src/SummaryRow.tsx index 3856d7c6ce..7a8cc5a279 100644 --- a/src/SummaryRow.tsx +++ b/src/SummaryRow.tsx @@ -1,4 +1,3 @@ -import { memo } from 'react'; import { css } from '@linaria/core'; import clsx from 'clsx'; @@ -50,7 +49,7 @@ const topSummaryRow = css` const summaryRowClassname = `rdg-summary-row ${summaryRow}`; -function SummaryRow({ +export default function SummaryRow({ rowIdx, gridRowStart, row, @@ -112,5 +111,3 @@ function SummaryRow({ ); } - -export default memo(SummaryRow) as (props: SummaryRowProps) => React.JSX.Element; diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index 78f800bc82..1a2b311399 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -1,4 +1,3 @@ -import { useCallback, useMemo } from 'react'; import type { Key } from 'react'; import { useLatestFunc } from './hooks'; @@ -77,43 +76,44 @@ export function TreeDataGrid({ const toggleGroupLatest = useLatestFunc(toggleGroup); const groupIdGetter = rawGroupIdGetter ?? defaultGroupIdGetter; - const { columns, groupBy } = useMemo(() => { - const columns = [...rawColumns].sort(({ key: aKey }, { key: bKey }) => { - // Sort select column first: - if (aKey === SELECT_COLUMN_KEY) return -1; - if (bKey === SELECT_COLUMN_KEY) return 1; + const columns = [...rawColumns].sort(({ key: aKey }, { key: bKey }) => { + // Sort select column first: + if (aKey === SELECT_COLUMN_KEY) return -1; + if (bKey === SELECT_COLUMN_KEY) return 1; - // Sort grouped columns second, following the groupBy order: - if (rawGroupBy.includes(aKey)) { - if (rawGroupBy.includes(bKey)) { - return rawGroupBy.indexOf(aKey) - rawGroupBy.indexOf(bKey); - } - return -1; - } - if (rawGroupBy.includes(bKey)) return 1; - - // Sort other columns last: - return 0; - }); - - const groupBy: string[] = []; - for (const [index, column] of columns.entries()) { - if (rawGroupBy.includes(column.key)) { - groupBy.push(column.key); - columns[index] = { - ...column, - frozen: true, - renderCell: () => null, - renderGroupCell: column.renderGroupCell ?? renderToggleGroup, - editable: false - }; + // Sort grouped columns second, following the groupBy order: + if (rawGroupBy.includes(aKey)) { + if (rawGroupBy.includes(bKey)) { + return rawGroupBy.indexOf(aKey) - rawGroupBy.indexOf(bKey); } + return -1; + } + if (rawGroupBy.includes(bKey)) return 1; + + // Sort other columns last: + return 0; + }); + + const groupBy: string[] = []; + for (const [index, column] of columns.entries()) { + if (rawGroupBy.includes(column.key)) { + groupBy.push(column.key); + columns[index] = { + ...column, + frozen: true, + renderCell: () => null, + renderGroupCell: column.renderGroupCell ?? renderToggleGroup, + editable: false + }; } + } - return { columns, groupBy }; - }, [rawColumns, rawGroupBy]); + const [groupedRows, rowsCount] = getGroupedRows(); + const [rows, isGroupRow] = getFlattenedRows(); + const rowHeight = getRowHeight(); + const selectedRows = getSelectedRows(); - const [groupedRows, rowsCount] = useMemo(() => { + function getGroupedRows(): [Readonly> | undefined, number] { if (groupBy.length === 0) return [undefined, rawRows.length]; const groupRows = ( @@ -137,12 +137,12 @@ export function TreeDataGrid({ }; return groupRows(rawRows, groupBy, 0); - }, [groupBy, rowGrouper, rawRows]); + } - const [rows, isGroupRow] = useMemo((): [ + function getFlattenedRows(): [ ReadonlyArray>, (row: R | GroupRow) => row is GroupRow - ] => { + ] { const allGroupRows = new Set(); if (!groupedRows) return [rawRows, isGroupRow]; @@ -188,9 +188,9 @@ export function TreeDataGrid({ function isGroupRow(row: R | GroupRow): row is GroupRow { return allGroupRows.has(row); } - }, [expandedGroupIds, groupedRows, rawRows, groupIdGetter]); + } - const rowHeight = useMemo(() => { + function getRowHeight() { if (typeof rawRowHeight === 'function') { return (row: R | GroupRow): number => { if (isGroupRow(row)) { @@ -201,46 +201,40 @@ export function TreeDataGrid({ } return rawRowHeight; - }, [isGroupRow, rawRowHeight]); - - const getParentRowAndIndex = useCallback( - (row: R | GroupRow) => { - const rowIdx = rows.indexOf(row); - for (let i = rowIdx - 1; i >= 0; i--) { - const parentRow = rows[i]; - if (isGroupRow(parentRow) && (!isGroupRow(row) || row.parentId === parentRow.id)) { - return [parentRow, i] as const; - } + } + + function getParentRowAndIndex(row: R | GroupRow) { + const rowIdx = rows.indexOf(row); + for (let i = rowIdx - 1; i >= 0; i--) { + const parentRow = rows[i]; + if (isGroupRow(parentRow) && (!isGroupRow(row) || row.parentId === parentRow.id)) { + return [parentRow, i] as const; } + } - return undefined; - }, - [isGroupRow, rows] - ); + return undefined; + } - const rowKeyGetter = useCallback( - (row: R | GroupRow) => { - if (isGroupRow(row)) { - return row.id; - } + function rowKeyGetter(row: R | GroupRow) { + if (isGroupRow(row)) { + return row.id; + } - if (typeof rawRowKeyGetter === 'function') { - return rawRowKeyGetter(row); - } + if (typeof rawRowKeyGetter === 'function') { + return rawRowKeyGetter(row); + } - const parentRowAndIndex = getParentRowAndIndex(row); - if (parentRowAndIndex !== undefined) { - const { startRowIndex, childRows } = parentRowAndIndex[0]; - const groupIndex = childRows.indexOf(row); - return startRowIndex + groupIndex + 1; - } + const parentRowAndIndex = getParentRowAndIndex(row); + if (parentRowAndIndex !== undefined) { + const { startRowIndex, childRows } = parentRowAndIndex[0]; + const groupIndex = childRows.indexOf(row); + return startRowIndex + groupIndex + 1; + } - return rows.indexOf(row); - }, - [getParentRowAndIndex, isGroupRow, rawRowKeyGetter, rows] - ); + return rows.indexOf(row); + } - const selectedRows = useMemo((): Maybe> => { + function getSelectedRows(): Maybe> { if (rawSelectedRows == null) return null; assertIsValidKeyGetter(rawRowKeyGetter); @@ -259,7 +253,7 @@ export function TreeDataGrid({ } return selectedRows; - }, [isGroupRow, rawRowKeyGetter, rawSelectedRows, rows]); + } function onSelectedRowsChange(newSelectedRows: Set) { if (!rawOnSelectedRowsChange) return; diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 0bbc33d0c9..9d888689d5 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { clampColumnWidth, max, min } from '../utils'; import type { CalculatedColumn, CalculatedColumnParent, ColumnOrColumnGroup, Omit } from '../types'; import { renderValue } from '../cellRenderers'; @@ -37,7 +35,7 @@ interface CalculatedColumnsArgs { enableVirtualization: boolean; } -export function useCalculatedColumns({ +export function getCalculatedColumns({ rawColumns, defaultColumnOptions, getColumnWidth, @@ -54,12 +52,18 @@ export function useCalculatedColumns({ const defaultResizable = defaultColumnOptions?.resizable ?? false; const defaultDraggable = defaultColumnOptions?.draggable ?? false; - const { columns, colSpanColumns, lastFrozenColumnIndex, headerRowsCount } = useMemo((): { + const { columns, colSpanColumns, lastFrozenColumnIndex, headerRowsCount } = + getCalculatedColumns(); + const { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics } = + getTemplateColumns(); + const [colOverscanStartIdx, colOverscanEndIdx] = getColOverscan(); + + function getCalculatedColumns(): { readonly columns: readonly CalculatedColumn[]; readonly colSpanColumns: readonly CalculatedColumn[]; readonly lastFrozenColumnIndex: number; readonly headerRowsCount: number; - } => { + } { let lastFrozenColumnIndex = -1; let headerRowsCount = 1; const columns: MutableCalculatedColumn[] = []; @@ -150,24 +154,14 @@ export function useCalculatedColumns({ lastFrozenColumnIndex, headerRowsCount }; - }, [ - rawColumns, - defaultWidth, - defaultMinWidth, - defaultMaxWidth, - defaultRenderCell, - defaultRenderHeaderCell, - defaultResizable, - defaultSortable, - defaultDraggable - ]); - - const { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics } = useMemo((): { + } + + function getTemplateColumns(): { templateColumns: readonly string[]; layoutCssVars: Readonly>; totalFrozenColumnWidth: number; columnMetrics: ReadonlyMap, ColumnMetric>; - } => { + } { const columnMetrics = new Map, ColumnMetric>(); let left = 0; let totalFrozenColumnWidth = 0; @@ -201,9 +195,9 @@ export function useCalculatedColumns({ } return { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics }; - }, [getColumnWidth, columns, lastFrozenColumnIndex]); + } - const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => { + function getColOverscan(): [number, number] { if (!enableVirtualization) { return [0, columns.length - 1]; } @@ -247,15 +241,7 @@ export function useCalculatedColumns({ const colOverscanEndIdx = min(lastColIdx, colVisibleEndIdx + 1); return [colOverscanStartIdx, colOverscanEndIdx]; - }, [ - columnMetrics, - columns, - lastFrozenColumnIndex, - scrollLeft, - totalFrozenColumnWidth, - viewportWidth, - enableVirtualization - ]); + } return { columns, diff --git a/src/hooks/useViewportColumns.ts b/src/hooks/useViewportColumns.ts index 701b719ec6..e36be66aa0 100644 --- a/src/hooks/useViewportColumns.ts +++ b/src/hooks/useViewportColumns.ts @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { getColSpan } from '../utils'; import type { CalculatedColumn, Maybe } from '../types'; @@ -16,7 +14,7 @@ interface ViewportColumnsArgs { rowOverscanEndIdx: number; } -export function useViewportColumns({ +export function getViewportColumns({ columns, colSpanColumns, rows, @@ -29,14 +27,15 @@ export function useViewportColumns({ rowOverscanEndIdx }: ViewportColumnsArgs) { // find the column that spans over a column within the visible columns range and adjust colOverscanStartIdx - const startIdx = useMemo(() => { + const startIdx = getStartIdx(); + + function getStartIdx() { if (colOverscanStartIdx === 0) return 0; let startIdx = colOverscanStartIdx; const updateStartIdx = (colIdx: number, colSpan: number | undefined) => { if (colSpan !== undefined && colIdx + colSpan > colOverscanStartIdx) { - // eslint-disable-next-line react-compiler/react-compiler startIdx = colIdx; return true; } @@ -90,26 +89,15 @@ export function useViewportColumns({ } return startIdx; - }, [ - rowOverscanStartIdx, - rowOverscanEndIdx, - rows, - topSummaryRows, - bottomSummaryRows, - colOverscanStartIdx, - lastFrozenColumnIndex, - colSpanColumns - ]); + } - return useMemo((): readonly CalculatedColumn[] => { - const viewportColumns: CalculatedColumn[] = []; - for (let colIdx = 0; colIdx <= colOverscanEndIdx; colIdx++) { - const column = columns[colIdx]; + const viewportColumns: CalculatedColumn[] = []; + for (let colIdx = 0; colIdx <= colOverscanEndIdx; colIdx++) { + const column = columns[colIdx]; - if (colIdx < startIdx && !column.frozen) continue; - viewportColumns.push(column); - } + if (colIdx < startIdx && !column.frozen) continue; + viewportColumns.push(column); + } - return viewportColumns; - }, [startIdx, colOverscanEndIdx, columns]); + return viewportColumns; } diff --git a/src/hooks/useViewportRows.ts b/src/hooks/useViewportRows.ts index c4af65e820..ea457cad22 100644 --- a/src/hooks/useViewportRows.ts +++ b/src/hooks/useViewportRows.ts @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { floor, max, min } from '../utils'; interface ViewportRowsArgs { @@ -10,14 +8,17 @@ interface ViewportRowsArgs { enableVirtualization: boolean; } -export function useViewportRows({ +export function getViewportRows({ rows, rowHeight, clientHeight, scrollTop, enableVirtualization }: ViewportRowsArgs) { - const { totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = useMemo(() => { + const { totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = + getTemplateRows(); + + function getTemplateRows() { if (typeof rowHeight === 'number') { return { totalRowHeight: rowHeight * rows.length, @@ -70,7 +71,7 @@ export function useViewportRows({ return 0; } }; - }, [rowHeight, rows]); + } let rowOverscanStartIdx = 0; let rowOverscanEndIdx = rows.length - 1; diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index 2a3efc9dfc..5eaee45593 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { createPortal } from 'react-dom'; import { page, userEvent } from '@vitest/browser/context'; @@ -357,43 +357,41 @@ function EditorTest({ }: EditorTestProps) { const [rows, setRows] = useState(gridRows); - const columns = useMemo((): readonly Column[] => { - return [ - { - key: 'col1', - name: 'Col1', - renderEditCell(p) { - return ( - p.onRowChange({ ...p.row, col1: e.target.valueAsNumber })} - /> - ); - } - }, - { - key: 'col2', - name: 'Col2', - editable, - renderEditCell({ row, onRowChange }) { - const editor = ( - onRowChange({ ...row, col2: e.target.value })} - /> - ); - - return createEditorPortal ? createPortal(editor, document.body) : editor; - }, - editorOptions + const columns: readonly Column[] = [ + { + key: 'col1', + name: 'Col1', + renderEditCell(p) { + return ( + p.onRowChange({ ...p.row, col1: e.target.valueAsNumber })} + /> + ); } - ]; - }, [editable, editorOptions, createEditorPortal]); + }, + { + key: 'col2', + name: 'Col2', + editable, + renderEditCell({ row, onRowChange }) { + const editor = ( + onRowChange({ ...row, col2: e.target.value })} + /> + ); + + return createEditorPortal ? createPortal(editor, document.body) : editor; + }, + editorOptions + } + ]; return ( <> diff --git a/vite.config.ts b/vite.config.ts index 7ec2496bf9..c92be173a2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -62,7 +62,10 @@ export default defineConfig(({ command }) => ({ autoCodeSplitting: true }), react({ - exclude: ['./.cache/**/*'] + exclude: ['./.cache/**/*'], + babel: { + plugins: [['babel-plugin-react-compiler', { target: '19' }]] + } }), wyw({ exclude: ['./.cache/**/*'], diff --git a/website/routes/ColumnsReordering.tsx b/website/routes/ColumnsReordering.tsx index 7793008218..97c13bfcfc 100644 --- a/website/routes/ColumnsReordering.tsx +++ b/website/routes/ColumnsReordering.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import { DataGrid } from '../../src'; @@ -76,16 +76,12 @@ function ColumnsReordering() { const [rows] = useState(createRows); const [columnsOrder, setColumnsOrder] = useState(initialColumnsOrder); const [sortColumns, setSortColumns] = useState([]); - const onSortColumnsChange = useCallback((sortColumns: SortColumn[]) => { - setSortColumns(sortColumns.slice(-1)); - }, []); const [columnWidths, setColumnWidths] = useState((): ColumnWidths => new Map()); - const reorderedColumns = useMemo(() => { - return columnsOrder.map((index) => columns[index]); - }, [columnsOrder]); + const reorderedColumns = columnsOrder.map((index) => columns[index]); + const sortedRows = getSortedRows(); - const sortedRows = useMemo((): readonly Row[] => { + function getSortedRows(): readonly Row[] { if (sortColumns.length === 0) return rows; const { columnKey, direction } = sortColumns[0]; @@ -103,7 +99,11 @@ function ColumnsReordering() { default: } return direction === 'DESC' ? sortedRows.reverse() : sortedRows; - }, [rows, sortColumns]); + } + + function onSortColumnsChange(sortColumns: SortColumn[]) { + setSortColumns(sortColumns.slice(-1)); + } function onColumnsReorder(sourceKey: string, targetKey: string) { setColumnsOrder((columnsOrder) => { diff --git a/website/routes/CommonFeatures.tsx b/website/routes/CommonFeatures.tsx index b938773043..31351c2e87 100644 --- a/website/routes/CommonFeatures.tsx +++ b/website/routes/CommonFeatures.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { createPortal, flushSync } from 'react-dom'; import { faker } from '@faker-js/faker'; import { createFileRoute } from '@tanstack/react-router'; @@ -322,19 +322,17 @@ function CommonFeatures() { const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); const [isExporting, setIsExporting] = useState(false); const gridRef = useRef(null); - const columns = useMemo(() => getColumns(countries, direction), [direction]); - - const summaryRows = useMemo((): readonly SummaryRow[] => { - return [ - { - id: 'total_0', - totalCount: rows.length, - yesCount: rows.filter((r) => r.available).length - } - ]; - }, [rows]); + const columns = getColumns(countries, direction); + const sortedRows = getSortedRows(); + const summaryRows: readonly SummaryRow[] = [ + { + id: 'total_0', + totalCount: rows.length, + yesCount: rows.filter((r) => r.available).length + } + ]; - const sortedRows = useMemo((): readonly Row[] => { + function getSortedRows(): readonly Row[] { if (sortColumns.length === 0) return rows; return [...rows].sort((a, b) => { @@ -347,7 +345,7 @@ function CommonFeatures() { } return 0; }); - }, [rows, sortColumns]); + } function handleExportToCsv() { flushSync(() => { diff --git a/website/routes/CustomizableRenderers.tsx b/website/routes/CustomizableRenderers.tsx index 4719cc1a37..3b0e1f8297 100644 --- a/website/routes/CustomizableRenderers.tsx +++ b/website/routes/CustomizableRenderers.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; @@ -88,8 +88,9 @@ function CustomizableRenderers() { const [rows, setRows] = useState(createRows); const [sortColumns, setSortColumns] = useState([]); const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); + const sortedRows = getSortedRows(); - const sortedRows = useMemo((): readonly Row[] => { + function getSortedRows(): readonly Row[] { if (sortColumns.length === 0) return rows; return [...rows].sort((a, b) => { @@ -102,7 +103,7 @@ function CustomizableRenderers() { } return 0; }); - }, [rows, sortColumns]); + } return ( - Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ - label: d, - value: d - })), - [rows] - ); + const developerOptions = Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ + label: d, + value: d + })); - const columns = useMemo((): readonly Column[] => { - return [ - { - key: 'id', - name: 'ID', - width: 50 - }, - { - key: 'task', - name: 'Title', - headerCellClass: filterColumnClassName, - renderHeaderCell: (p) => ( - {...p}> - {({ filters, ...rest }) => ( - - setFilters({ - ...filters, - task: e.target.value - }) - } - onKeyDown={inputStopPropagation} - /> - )} - - ) - }, - { - key: 'priority', - name: 'Priority', - headerCellClass: filterColumnClassName, - renderHeaderCell: (p) => ( - {...p}> - {({ filters, ...rest }) => ( - - )} - - ) - }, - { - key: 'issueType', - name: 'Issue Type', - headerCellClass: filterColumnClassName, - renderHeaderCell: (p) => ( - {...p}> - {({ filters, ...rest }) => ( - - )} - - ) - }, - { - key: 'developer', - name: 'Developer', - headerCellClass: filterColumnClassName, - renderHeaderCell: (p) => ( - {...p}> - {({ filters, ...rest }) => ( - - setFilters({ - ...filters, - developer: e.target.value - }) - } - onKeyDown={inputStopPropagation} - list="developers" - /> - )} - - ) - }, - { - key: 'complete', - name: '% Complete', - headerCellClass: filterColumnClassName, - renderHeaderCell: (p) => ( - {...p}> - {({ filters, ...rest }) => ( - - setFilters({ - ...filters, - complete: Number.isFinite(e.target.valueAsNumber) - ? e.target.valueAsNumber - : undefined - }) - } - onKeyDown={inputStopPropagation} - /> - )} - - ) - } - ]; - }, []); + const columns: readonly Column[] = [ + { + key: 'id', + name: 'ID', + width: 50 + }, + { + key: 'task', + name: 'Title', + headerCellClass: filterColumnClassName, + renderHeaderCell: (p) => ( + {...p}> + {({ filters, ...rest }) => ( + + setFilters({ + ...filters, + task: e.target.value + }) + } + onKeyDown={inputStopPropagation} + /> + )} + + ) + }, + { + key: 'priority', + name: 'Priority', + headerCellClass: filterColumnClassName, + renderHeaderCell: (p) => ( + {...p}> + {({ filters, ...rest }) => ( + + )} + + ) + }, + { + key: 'issueType', + name: 'Issue Type', + headerCellClass: filterColumnClassName, + renderHeaderCell: (p) => ( + {...p}> + {({ filters, ...rest }) => ( + + )} + + ) + }, + { + key: 'developer', + name: 'Developer', + headerCellClass: filterColumnClassName, + renderHeaderCell: (p) => ( + {...p}> + {({ filters, ...rest }) => ( + + setFilters({ + ...filters, + developer: e.target.value + }) + } + onKeyDown={inputStopPropagation} + list="developers" + /> + )} + + ) + }, + { + key: 'complete', + name: '% Complete', + headerCellClass: filterColumnClassName, + renderHeaderCell: (p) => ( + {...p}> + {({ filters, ...rest }) => ( + + setFilters({ + ...filters, + complete: Number.isFinite(e.target.valueAsNumber) + ? e.target.valueAsNumber + : undefined + }) + } + onKeyDown={inputStopPropagation} + /> + )} + + ) + } + ]; - const filteredRows = useMemo(() => { - return rows.filter((r) => { - return ( - (filters.task ? r.task.includes(filters.task) : true) && - (filters.priority !== 'All' ? r.priority === filters.priority : true) && - (filters.issueType !== 'All' ? r.issueType === filters.issueType : true) && - (filters.developer - ? r.developer.toLowerCase().startsWith(filters.developer.toLowerCase()) - : true) && - (filters.complete !== undefined ? r.complete >= filters.complete : true) - ); - }); - }, [rows, filters]); + const filteredRows = rows.filter((r) => { + return ( + (filters.task ? r.task.includes(filters.task) : true) && + (filters.priority !== 'All' ? r.priority === filters.priority : true) && + (filters.issueType !== 'All' ? r.issueType === filters.issueType : true) && + (filters.developer + ? r.developer.toLowerCase().startsWith(filters.developer.toLowerCase()) + : true) && + (filters.complete !== undefined ? r.complete >= filters.complete : true) + ); + }); function clearFilters() { setFilters({ diff --git a/website/routes/MasterDetail.tsx b/website/routes/MasterDetail.tsx index 96abe1e952..4ec2c1f369 100644 --- a/website/routes/MasterDetail.tsx +++ b/website/routes/MasterDetail.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { faker } from '@faker-js/faker'; import { createFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; @@ -72,44 +72,41 @@ const productColumns: readonly Column[] = [ function MasterDetail() { const direction = useDirection(); - - const columns = useMemo((): readonly Column[] => { - return [ - { - key: 'expanded', - name: '', - minWidth: 30, - width: 30, - colSpan(args) { - return args.type === 'ROW' && args.row.type === 'DETAIL' ? 3 : undefined; - }, - cellClass(row) { - return row.type === 'DETAIL' - ? css` - padding: 24px; - ` - : undefined; - }, - renderCell({ row, tabIndex, onRowChange }) { - if (row.type === 'DETAIL') { - return ; - } - - return ( - { - onRowChange({ ...row, expanded: !row.expanded }); - }} - /> - ); - } + const columns: readonly Column[] = [ + { + key: 'expanded', + name: '', + minWidth: 30, + width: 30, + colSpan(args) { + return args.type === 'ROW' && args.row.type === 'DETAIL' ? 3 : undefined; + }, + cellClass(row) { + return row.type === 'DETAIL' + ? css` + padding: 24px; + ` + : undefined; }, - { key: 'id', name: 'ID', width: 35 }, - { key: 'department', name: 'Department' } - ]; - }, [direction]); + renderCell({ row, tabIndex, onRowChange }) { + if (row.type === 'DETAIL') { + return ; + } + + return ( + { + onRowChange({ ...row, expanded: !row.expanded }); + }} + /> + ); + } + }, + { key: 'id', name: 'ID', width: 35 }, + { key: 'department', name: 'Department' } + ]; const [rows, setRows] = useState(createDepartments); function onRowsChange(rows: DepartmentRow[], { indexes }: RowsChangeData) { diff --git a/website/routes/RowsReordering.tsx b/website/routes/RowsReordering.tsx index 3c17e78ccb..ceccecbf30 100644 --- a/website/routes/RowsReordering.tsx +++ b/website/routes/RowsReordering.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useState } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { createFileRoute } from '@tanstack/react-router'; @@ -65,7 +65,7 @@ function RowsReordering() { const direction = useDirection(); const [rows, setRows] = useState(createRows); - const renderRow = useCallback((key: React.Key, props: RenderRowProps) => { + function renderRow(key: React.Key, props: RenderRowProps) { function onRowReorder(fromIndex: number, toIndex: number) { setRows((rows) => { const newRows = [...rows]; @@ -75,7 +75,7 @@ function RowsReordering() { } return ; - }, []); + } return ( diff --git a/website/routes/TreeView.tsx b/website/routes/TreeView.tsx index 914a2abdf8..6de3497d45 100644 --- a/website/routes/TreeView.tsx +++ b/website/routes/TreeView.tsx @@ -1,4 +1,4 @@ -import { useMemo, useReducer, useState } from 'react'; +import { useReducer, useState } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; @@ -127,61 +127,59 @@ function TreeView() { const [rows, dispatch] = useReducer(reducer, defaultRows); const [allowDelete, setAllowDelete] = useState(true); - const columns = useMemo((): readonly Column[] => { - return [ - { - key: 'id', - name: 'id', - frozen: true - }, - { - key: 'name', - name: 'Name' - }, - { - key: 'format', - name: 'format', - renderCell({ row, tabIndex }) { - const hasChildren = row.children !== undefined; - const style = hasChildren ? undefined : { marginInlineStart: 30 }; - return ( -
- {hasChildren && ( - dispatch({ id: row.id, type: 'toggleSubRow' })} - /> - )} - {!hasChildren && ( - dispatch({ id: row.id, type: 'deleteSubRow' })} - /> - )} -
{row.format}
-
- ); - } - }, - { - key: 'position', - name: 'position' - }, - { - key: 'price', - name: 'price' + const columns: readonly Column[] = [ + { + key: 'id', + name: 'id', + frozen: true + }, + { + key: 'name', + name: 'Name' + }, + { + key: 'format', + name: 'format', + renderCell({ row, tabIndex }) { + const hasChildren = row.children !== undefined; + const style = hasChildren ? undefined : { marginInlineStart: 30 }; + return ( +
+ {hasChildren && ( + dispatch({ id: row.id, type: 'toggleSubRow' })} + /> + )} + {!hasChildren && ( + dispatch({ id: row.id, type: 'deleteSubRow' })} + /> + )} +
{row.format}
+
+ ); } - ]; - }, [allowDelete]); + }, + { + key: 'position', + name: 'position' + }, + { + key: 'price', + name: 'price' + } + ]; return ( <>