Skip to content
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