Skip to content

Commit 9b48ec5

Browse files
authored
Fix virtualizer bug when an item changes parents (#6723)
1 parent a5744be commit 9b48ec5

File tree

3 files changed

+63
-3
lines changed

3 files changed

+63
-3
lines changed

packages/@react-spectrum/listbox/test/ListBox.test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,60 @@ describe('ListBox', function () {
759759
expect(option.id).not.toEqual('Foo');
760760
});
761761

762+
it('should handle when an item changes sections', function () {
763+
let sections = [
764+
{
765+
id: 'foo',
766+
title: 'Foo',
767+
children: [
768+
{id: 'foo-1', title: 'Foo 1'},
769+
{id: 'foo-2', title: 'Foo 2'}
770+
]
771+
},
772+
{
773+
id: 'bar',
774+
title: 'Bar',
775+
children: [
776+
{id: 'bar-1', title: 'Bar 1'},
777+
{id: 'bar-2', title: 'Bar 2'}
778+
]
779+
}
780+
];
781+
782+
function Example({sections}) {
783+
return (
784+
<Provider theme={theme}>
785+
<ListBox aria-label="listbox" items={sections}>
786+
{section => (
787+
<Section title={section.title} items={section.children}>
788+
{item => <Item>{item.title}</Item>}
789+
</Section>
790+
)}
791+
</ListBox>
792+
</Provider>
793+
);
794+
}
795+
796+
let {getByText, rerender} = render(<Example sections={sections} />);
797+
let item = getByText('Foo 1');
798+
expect(document.getElementById(item.closest('[role=group]').getAttribute('aria-labelledby'))).toHaveTextContent('Foo');
799+
800+
let sections2 = [
801+
{
802+
...sections[0],
803+
children: [sections[0].children[1]]
804+
},
805+
{
806+
...sections[1],
807+
children: [...sections[1].children, sections[0].children[0]]
808+
}
809+
];
810+
811+
rerender(<Example sections={sections2} />);
812+
item = getByText('Foo 1');
813+
expect(document.getElementById(item.closest('[role=group]').getAttribute('aria-labelledby'))).toHaveTextContent('Bar');
814+
});
815+
762816
describe('async loading', function () {
763817
it('should display a spinner while loading', async function () {
764818
let {getByRole, rerender} = render(

packages/@react-stately/virtualizer/src/Virtualizer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,12 @@ export class Virtualizer<T extends object, V> {
101101
return false;
102102
}
103103

104+
private getParentView(layoutInfo: LayoutInfo): ReusableView<T, V> | undefined {
105+
return layoutInfo.parentKey != null ? this._visibleViews.get(layoutInfo.parentKey) : this._rootView;
106+
}
107+
104108
private getReusableView(layoutInfo: LayoutInfo): ReusableView<T, V> {
105-
let parentView = layoutInfo.parentKey != null ? this._visibleViews.get(layoutInfo.parentKey) : this._rootView;
109+
let parentView = this.getParentView(layoutInfo)!;
106110
let view = parentView.getReusableView(layoutInfo.type);
107111
view.layoutInfo = layoutInfo;
108112
this._renderView(view);
@@ -194,7 +198,9 @@ export class Virtualizer<T extends object, V> {
194198

195199
let removed = new Set<ReusableView<T, V>>();
196200
for (let [key, view] of this._visibleViews) {
197-
if (!visibleLayoutInfos.has(key)) {
201+
let layoutInfo = visibleLayoutInfos.get(key);
202+
// If a view's parent changed, treat it as a delete and re-create in the new parent.
203+
if (!layoutInfo || view.parent !== this.getParentView(layoutInfo)) {
198204
this._visibleViews.delete(key);
199205
view.parent.reuseChild(view);
200206
removed.add(view); // Defer removing in case we reuse this view.

packages/dev/docs/src/DocSearch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default function DocSearch() {
127127

128128
sections.push({title, items: items.map((item) => item[0])});
129129
}
130-
let newSuggestions = sections.map((section, index) => ({key: `${index}-${section.title}`, title: section.title, children: section.items}));
130+
let newSuggestions = sections.map((section) => ({key: section.title, title: section.title, children: section.items}));
131131
newSuggestions.push({
132132
key: 'algolia-footer',
133133
title: 'Search by',

0 commit comments

Comments
 (0)