From 7c0759cefc07f92c5fa45dd17e1c812137cdfca5 Mon Sep 17 00:00:00 2001 From: Oliver Breitwieser Date: Wed, 23 Apr 2025 16:56:35 +0200 Subject: [PATCH 1/2] fix: TableView jump to top on asynchronous load --- packages/@react-stately/layout/src/TableLayout.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/@react-stately/layout/src/TableLayout.ts b/packages/@react-stately/layout/src/TableLayout.ts index 4c820b5b1e9..96b328d68b8 100644 --- a/packages/@react-stately/layout/src/TableLayout.ts +++ b/packages/@react-stately/layout/src/TableLayout.ts @@ -68,9 +68,12 @@ export class TableLayout exten // If columnWidths were provided via layoutOptions, update those. // Otherwise, calculate column widths ourselves. if (invalidationContext.layoutOptions?.columnWidths) { - if (invalidationContext.layoutOptions.columnWidths !== this.columnWidths) { - this.columnWidths = invalidationContext.layoutOptions.columnWidths; - invalidationContext.sizeChanged = true; + for (const [key, val] of invalidationContext.layoutOptions.columnWidths) { + if (this.columnWidths.get(key) !== val) { + this.columnWidths = invalidationContext.layoutOptions.columnWidths; + invalidationContext.sizeChanged = true; + break; + } } } else if (invalidationContext.sizeChanged || this.columnsChanged(newCollection, this.lastCollection)) { let columnLayout = new TableColumnLayout({}); From 56d447490d224073ebd493ad1940a92b69638e77 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Mon, 7 Jul 2025 16:27:05 -0700 Subject: [PATCH 2/2] add test story --- .../table/stories/Table.stories.tsx | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/packages/@react-spectrum/table/stories/Table.stories.tsx b/packages/@react-spectrum/table/stories/Table.stories.tsx index 96020a918ae..3c98f38b863 100644 --- a/packages/@react-spectrum/table/stories/Table.stories.tsx +++ b/packages/@react-spectrum/table/stories/Table.stories.tsx @@ -1485,14 +1485,13 @@ export const AsyncLoadingClientFiltering: TableStory = { name: 'async client side filter loading' }; +interface StarWarsItem { + name: string, + height: string, + mass: string +} function AsyncServerFilterTable(props) { - interface Item { - name: string, - height: string, - mass: string - } - let columns = [ { name: 'Name', @@ -1509,7 +1508,7 @@ function AsyncServerFilterTable(props) { } ]; - let list = useAsyncList({ + let list = useAsyncList({ getKey: (item) => item.name, async load({signal, cursor, filterText}) { if (cursor) { @@ -2197,4 +2196,67 @@ function LoadingTable() { ); } +function AsyncLoadOverflowWrapRepro() { + let columns = [ + {name: 'Name', key: 'name'}, + {name: 'Height', key: 'height'}, + {name: 'Mass', key: 'mass'}, + {name: 'Birth Year', key: 'birth_year'} + ]; + + let list = useAsyncList({ + async load({signal, cursor}) { + if (cursor) { + cursor = cursor.replace(/^http:\/\//i, 'https://'); + } + + let res = await fetch( + cursor || 'https://swapi.py4e.com/api/people/?search=', + {signal} + ); + let json = await res.json(); + + return { + items: json.results, + cursor: json.next + }; + } + }); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + {(key) => ( + {`${item[key]}++++${item[key]}++++${item[key]}++++`} + )} + + )} + + + ); +} + +export const AsyncLoadOverflowWrapReproStory: TableStory = { + render: (args) => , + name: 'async, overflow wrap scroll jumping reproduction', + parameters: {description: {data: ` + Rapidly scrolling down through this table should not cause the scroll position to jump to the top. + `}} +}; + export {Performance} from './Performance';