Skip to content

Commit 89c6870

Browse files
committed
fix(ScrollView): don't report empty visible rect when the content suspends
closes #8214 See also: #8215 (comment)
1 parent 77b3442 commit 89c6870

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

packages/@react-aria/virtualizer/src/ScrollView.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
184184
// adjusted space. In very specific cases this might result in the scrollbars disappearing
185185
// again, resulting in extra padding. We stop after a maximum of two layout passes to avoid
186186
// an infinite loop. This matches how browsers behavior with native CSS grid layout.
187-
if (!isTestEnv && clientWidth !== dom.clientWidth || clientHeight !== dom.clientHeight) {
187+
if (!isTestEnv && (dom.checkVisibility?.() ?? true) && (clientWidth !== dom.clientWidth || clientHeight !== dom.clientHeight)) {
188188
state.width = dom.clientWidth;
189189
state.height = dom.clientHeight;
190190
flush(() => {
@@ -198,7 +198,7 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
198198

199199
// Update visible rect when the content size changes, in case scrollbars need to appear or disappear.
200200
let lastContentSize = useRef<Size | null>(null);
201-
let [update, setUpdate] = useState({});
201+
let [update, setUpdate] = useState<{} | false>(false);
202202
useLayoutEffect(() => {
203203
if (!isUpdatingSize.current && (lastContentSize.current == null || !contentSize.equals(lastContentSize.current))) {
204204
// React doesn't allow flushSync inside effects, so queue a microtask.
@@ -224,7 +224,9 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
224224

225225
// Will only run in tests, needs to be in separate effect so it is properly run in the next render in strict mode.
226226
useLayoutEffect(() => {
227-
updateSize(fn => fn());
227+
if (update) {
228+
updateSize(fn => fn());
229+
}
228230
}, [update]);
229231

230232
let onResize = useCallback(() => {

packages/@react-spectrum/list/chromatic/ListView.stories.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,37 @@ export const Empty = {
168168
render: TemplateEmptyState,
169169
name: 'empty state'
170170
};
171+
172+
export const SuspendedItem = {
173+
render: (args) => {
174+
return (
175+
<React.Suspense fallback="loading...">
176+
<div style={{width: 200}}>
177+
<ListView {...args}>
178+
<Item>
179+
<Text>
180+
<WithSuspense>Item 1</WithSuspense>
181+
</Text>
182+
</Item>
183+
<Item>Item 2</Item>
184+
</ListView>
185+
</div>
186+
</React.Suspense>
187+
);
188+
},
189+
name: 'suspended item'
190+
};
191+
let data: string;
192+
const promise = new Promise((resolve) => setTimeout(resolve, 500)).then(() => {
193+
data = 'data';
194+
});
195+
const useData = () => {
196+
if (!data) {
197+
throw promise;
198+
}
199+
return data;
200+
};
201+
function WithSuspense({children}: { children: React.ReactNode }) {
202+
useData();
203+
return <>{children}</>;
204+
}

0 commit comments

Comments
 (0)