Skip to content

Tableview crash bug #5380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 11, 2023
15 changes: 13 additions & 2 deletions packages/@react-aria/virtualizer/src/ScrollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,27 @@ function ScrollView(props: ScrollViewProps, ref: RefObject<HTMLDivElement>) {
if (scrollDirection === 'horizontal') {
style.overflowX = 'auto';
style.overflowY = 'hidden';
} else if (scrollDirection === 'vertical') {
} else if (scrollDirection === 'vertical' || contentSize.width === state.width) {
// Set overflow-x: hidden if content size is equal to the width of the scroll view.
// This prevents horizontal scrollbars from flickering during resizing due to resize observer
// firing slower than the frame rate, which may cause an infinite re-render loop.
style.overflowY = 'auto';
style.overflowX = 'hidden';
} else {
style.overflow = 'auto';
}

innerStyle = {
width: contentSize.width,
height: contentSize.height,
pointerEvents: isScrolling ? 'none' : 'auto',
position: 'relative',
...innerStyle
};

return (
<div {...otherProps} style={style} ref={ref} onScroll={onScroll}>
<div role="presentation" style={{width: contentSize.width, height: contentSize.height, pointerEvents: isScrolling ? 'none' : 'auto', position: 'relative', ...innerStyle}}>
<div role="presentation" style={innerStyle}>
{children}
</div>
</div>
Expand Down
150 changes: 148 additions & 2 deletions packages/@react-spectrum/table/stories/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -927,8 +927,7 @@ function AsyncLoadingExample(props) {
let url = new URL('https://www.reddit.com/r/upliftingnews.json');
if (cursor) {
url.searchParams.append('after', cursor);
}

}
let res = await fetch(url.toString(), {signal});
let json = await res.json();
return {items: json.data.children, cursor: json.data.after};
Expand Down Expand Up @@ -983,6 +982,153 @@ export const AsyncLoading: TableStory = {
name: 'async loading'
};

async function fakeFetch() {
return new Promise(
(resolve) => {
setTimeout(() => resolve({json: async function () {
return {data: {children: [
{data: {id: 'foo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: 'fooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: 'foooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: 'fooooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: 'foooooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: 'doo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '1', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '2', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '3', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '4', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '5', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}},
{data: {id: '6', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}
]}};
}}), 1000);
}
);
}

export const AsyncLoadingQuarryTest: TableStory = {
args: {
'aria-label': 'Top news from Reddit',
selectionMode: 'multiple',
height: 400,
width: '100%'
},
render: (args) => <AsyncLoadingExampleQuarryTest {...args} />,
name: 'async reload on sort'
};

function AsyncLoadingExampleQuarryTest(props) {
let [filters, setFilters] = React.useState({});

const rColumns = [
{
key: 'title',
label: 'Title'
},
{
key: 'ups',
label: 'Upvotes',
width: 200
},
{
key: 'created',
label: 'Created',
width: 350
}
];
interface Post {
id: string,
key: string,
preview?: string,
ups: number,
title: string,
created: string,
url: string
}

let list = useAsyncList<Post>({
getKey: (item) => item.id,
async load({cursor}) {
const url = new URL('https://www.reddit.com/r/aww.json');

if (cursor) {
url.searchParams.append('after', cursor);
}

const res = await fakeFetch();
// @ts-ignore
const json = await res.json();
const items = json.data.children.map((item) => {
return {
id: item.data.id,
preview: item.data.thumbnail,
ups: item.data.ups,
title: item.data.title,
created: new Date(item.data.created * 1000).toISOString()
};
});
return {
items,
cursor: json.data.after,
total: 987
};
},
async sort({items, sortDescriptor}) {
return {
items: items.slice().sort((a, b) => {
let cmp = a[sortDescriptor.column] < b[sortDescriptor.column] ? -1 : 1;

if (sortDescriptor.direction === 'descending') {
cmp *= -1;
}

return cmp;
})

};
}
});

let reloadDeps = [filters];

useMountEffect((): void => {
list.reload();
}, reloadDeps);

return (
<TableView {...props} width="90vw" sortDescriptor={list.sortDescriptor} selectedKeys={list.selectedKeys} onSelectionChange={list.setSelectedKeys} onSortChange={setFilters}>
<TableHeader columns={rColumns}>
{(column) => (
<Column key={column.key} allowsSorting>
{column.label}
</Column>
)}
</TableHeader>
<TableBody items={list.items} loadingState={list.loadingState} onLoadMore={list.loadMore}>
{item =>
(<Row key={item.id}>
{key =>
key === 'title'
? <Cell textValue={item.title}><Link isQuiet><a href={item.url} target="_blank">{item.title}</a></Link></Cell>
: <Cell>{item[key]}</Cell>
}
</Row>)
}
</TableBody>
</TableView>
);
}

function useMountEffect(fn: () => void, deps: Array<unknown>): void {
const mounted = React.useRef(false);
React.useEffect(() => {
if (mounted.current) {
fn();
} else {
mounted.current = true;
}
}, deps);
}

export const HideHeader: TableStory = {
args: {
'aria-label': 'TableView with static contents',
Expand Down