From 4551e74bc362641e1aa588a06128374951451d5d Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 13 Apr 2026 14:30:52 +0200 Subject: [PATCH 1/2] fix(AnalyticalTable): recalc column widths after `columns` change with `retainColumnWidth` --- .../AnalyticalTable/AnalyticalTable.cy.tsx | 55 +++++++++++++++++++ .../tableReducer/stateReducer.ts | 3 + 2 files changed, 58 insertions(+) diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx index b8a8b7c616c..4289aadaddf 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx @@ -4826,6 +4826,61 @@ describe('AnalyticalTable', () => { cy.get('[data-component-name="AnalyticalTableBody"]').should('have.prop', 'scrollTop', 2500); }); + it('retainColumnWidth: recalculates widths after columns change', () => { + const columnsA = [ + { Header: 'Name', accessor: 'name' }, + { Header: 'Age', accessor: 'age' }, + ]; + const columnsB = [ + { Header: 'Product', accessor: 'product' }, + { Header: 'Price', accessor: 'price' }, + { Header: 'Qty', accessor: 'qty' }, + ]; + const dataA = [ + { name: 'Alice', age: 30 }, + { name: 'Bob', age: 25 }, + ]; + const dataB = [ + { product: 'Widget', price: '$10', qty: 5 }, + { product: 'Gadget', price: '$20', qty: 3 }, + ]; + + function TestComp() { + const [useB, setUseB] = useState(false); + return ( + <> + + + + ); + } + + cy.mount(); + cy.get('[data-column-id="name"]').invoke('outerWidth').should('be.gt', 150).as('initialWidth'); + + // resize first column + cy.get('[data-component-name="AnalyticalTableResizer"]') + .eq(0) + .realMouseDown() + .realMouseMove(-50, 0, { scrollBehavior: false }); + cy.get('body').realMouseUp(); + cy.get('@initialWidth').then((initialWidth) => { + cy.get('[data-column-id="name"]').invoke('outerWidth').should('not.eq', initialWidth); + }); + + cy.get('[data-testid="switch"]').click(); + cy.get('[data-column-id="product"]').invoke('outerWidth').should('be.gt', 150); + cy.get('[data-column-id="price"]').invoke('outerWidth').should('be.gt', 150); + cy.get('[data-column-id="qty"]').invoke('outerWidth').should('be.gt', 150); + }); + cypressPassThroughTestsFactory(AnalyticalTable, { data, columns }); }); diff --git a/packages/main/src/components/AnalyticalTable/tableReducer/stateReducer.ts b/packages/main/src/components/AnalyticalTable/tableReducer/stateReducer.ts index 73b7156bae8..0af59677b2e 100644 --- a/packages/main/src/components/AnalyticalTable/tableReducer/stateReducer.ts +++ b/packages/main/src/components/AnalyticalTable/tableReducer/stateReducer.ts @@ -67,6 +67,9 @@ export const stateReducer: TableInstance['stateReducer'] = (state, action, _prev return { ...state, subComponentsHeight: payload }; case 'TABLE_COL_RESIZED': return { ...state, tableColResized: payload }; + case 'resetResize': + // reset `tableColResized` state, when react-table resize state is reset + return { ...state, tableColResized: undefined }; case 'ROW_COLLAPSED_FLAG': return { ...state, rowCollapsed: payload }; case 'COLUMN_DND_START': From 213b911b1d7082b8d6871fffb544df09bfffb7af Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 13 Apr 2026 15:08:33 +0200 Subject: [PATCH 2/2] update FAQ & CLAUDE.md --- packages/main/CLAUDE.md | 2 +- .../components/AnalyticalTable/docs/FAQ.mdx | 55 ++++++++++++++----- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/main/CLAUDE.md b/packages/main/CLAUDE.md index a97fad82570..2214e980596 100644 --- a/packages/main/CLAUDE.md +++ b/packages/main/CLAUDE.md @@ -446,7 +446,7 @@ useEffect(() => { +Full list of autoReset* options + +| `reactTableOptions` option | Default | Resets on | What it resets | +| -------------------------- | ------- | ---------------- | ------------------------------------------- | +| `autoResetSelectedRows` | `true` | `data` change | Row selection (`selectedRowIds`) | +| `autoResetSortBy` | `true` | `data` change | Sort state (unless `manualSortBy`) | +| `autoResetFilters` | `true` | `data` change | Filter state (unless `manualFilters`) | +| `autoResetGlobalFilter` | `true` | `data` change | Global filter (unless `manualGlobalFilter`) | +| `autoResetGroupBy` | `true` | `data` change | Grouping state (unless `manualGroupBy`) | +| `autoResetExpanded` | `true` | `data` change | Expanded rows | +| `autoResetHiddenColumns` | `true` | `data` change | Hidden columns | +| `autoResetResize` | `true` | `columns` change | User-resized column widths | + + + +**Important:** Column resize widths are never reset by `data` changes - only by `columns` changes. If you want to preserve user-resized widths across column changes, set `autoResetResize: false`. + +### `retainColumnWidth` prop + +`retainColumnWidth` is a separate mechanism from `autoResetResize`. It controls what happens on **container resizes** (e.g., window resize): + +- **Without** `retainColumnWidth`: A container resize clears user-resized column widths and triggers dynamic width recalculation. +- **With** `retainColumnWidth`: User-resized column widths are preserved across container resizes. + +### Keeping state across data updates + +To prevent automatic resets when updating data, set the corresponding `autoReset*` option to `false` in `reactTableOptions`. The ref-based flag shown below is optional - it avoids an unnecessary re-render by reading the flag during render instead of toggling state: ```jsx -const [data, setData] = React.useState([]) -const skipPageResetRef = React.useRef(false) +const [data, setData] = useState([]) +const skipPageResetRef = useRef(false) const updateData = newData => { - // When data gets updated with this function, set a flag - // to disable all of the auto resetting + // When data gets updated with this function, set a flag to disable all of the auto resetting skipPageResetRef.current = true setData(newData) } -React.useEffect(() => { - // After the table has updated, always remove the flag +useEffect(() => { + // After the table has updated, remove the flag skipPageResetRef.current = false }) ```