I am using react-query for infinite loading feed and '@tanstack/react-virtual'
for virtualization of the cards which are composed of mui elements. I want to keep my app height to 100vh so there's no whole screen scroll down. But the component which contains the feed can be scrollable. I use overflowY: 'scroll'
on it so I can scroll inside this element without scrolling down the screen.
The Problem is that when I apply overflowY: 'scroll'
to the div inside LogCards
which wraps the map iterations the scroll becomes very jittery, it flickers very fast as if it has trouble calculating scroll position properly and then it breaks and all cards disappear. How can I fix this? As soon as I remove overflowY: 'scroll'
and allow the whole screen to be scrolled the issue goes away. A short video of it.
Here's the component holding the feed:
const LogCards = ({ pageLimit }: LogCardsProps) => {
const {
data: logs,
fetchNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteLogsQuery(pageLimit);
const { ref, inView } = useInView();
const allLogs = useMemo(() => logs?.pages.flatMap((log) => log.results), [logs]) || [];
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: allLogs.length,
getScrollElement: useCallback(() => parentRef.current, []),
estimateSize: useCallback(() => 300, []),
overscan: 2,
gap: 8,
});
useEffect(() => {
if (inView) fetchNextPage();
}, [fetchNextPage, inView]);
return !isLoading ? (
<Box ref={parentRef} sx={{ overflowY: 'scroll' }}>
<div style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
{allLogs &&
rowVirtualizer.getVirtualItems().map((log, index) => {
const LogCardData = allLogs[log.index];
console.log(LogCardData);
if (!LogCardData) return <span key={`no card index ${index}`}>No cards</span>;
return (
<LogCard
key={LogCardData.id}
data-index={log.index}
ref={rowVirtualizer.measureElement}
log={LogCardData}
/>
);
})}
</div>
<Box sx={{ height: '20px' }} ref={ref}>
{isFetchingNextPage && <LinearProgress sx={{ m: '2px' }} />}
</Box>
</Box>
) : (
<LinearLoader />
);
};
Here's the component which holds LogCards:
const LogsList = () => {
const [pageLimit, setPageLimit] = useState(25);
return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
height: '100vh',
px: 1,
borderRadius: { xs: '4px', md: 0 },
}}
>
<Typography variant="h5" textAlign="center" sx={{ py: 2 }}>
List of unsolved logs
</Typography>
<LogsLimitButtons pageLimit={pageLimit} setPageLimit={setPageLimit} />
<LogCards pageLimit={pageLimit} />
</Paper>
);
};
And here's general layout of the app:
const LaunchDesktop = () => {
return (
<Grid2 container spacing={1}>
<Grid2
size={{ xs: 12, md: 8 }}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<SosForm />
</Grid2>
<Grid2 size={{ xs: 12, md: 4 }}>
<LogsList />
</Grid2>
</Grid2>
);
};
I am using react-query for infinite loading feed and '@tanstack/react-virtual'
for virtualization of the cards which are composed of mui elements. I want to keep my app height to 100vh so there's no whole screen scroll down. But the component which contains the feed can be scrollable. I use overflowY: 'scroll'
on it so I can scroll inside this element without scrolling down the screen.
The Problem is that when I apply overflowY: 'scroll'
to the div inside LogCards
which wraps the map iterations the scroll becomes very jittery, it flickers very fast as if it has trouble calculating scroll position properly and then it breaks and all cards disappear. How can I fix this? As soon as I remove overflowY: 'scroll'
and allow the whole screen to be scrolled the issue goes away. A short video of it.
Here's the component holding the feed:
const LogCards = ({ pageLimit }: LogCardsProps) => {
const {
data: logs,
fetchNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteLogsQuery(pageLimit);
const { ref, inView } = useInView();
const allLogs = useMemo(() => logs?.pages.flatMap((log) => log.results), [logs]) || [];
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: allLogs.length,
getScrollElement: useCallback(() => parentRef.current, []),
estimateSize: useCallback(() => 300, []),
overscan: 2,
gap: 8,
});
useEffect(() => {
if (inView) fetchNextPage();
}, [fetchNextPage, inView]);
return !isLoading ? (
<Box ref={parentRef} sx={{ overflowY: 'scroll' }}>
<div style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
{allLogs &&
rowVirtualizer.getVirtualItems().map((log, index) => {
const LogCardData = allLogs[log.index];
console.log(LogCardData);
if (!LogCardData) return <span key={`no card index ${index}`}>No cards</span>;
return (
<LogCard
key={LogCardData.id}
data-index={log.index}
ref={rowVirtualizer.measureElement}
log={LogCardData}
/>
);
})}
</div>
<Box sx={{ height: '20px' }} ref={ref}>
{isFetchingNextPage && <LinearProgress sx={{ m: '2px' }} />}
</Box>
</Box>
) : (
<LinearLoader />
);
};
Here's the component which holds LogCards:
const LogsList = () => {
const [pageLimit, setPageLimit] = useState(25);
return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
height: '100vh',
px: 1,
borderRadius: { xs: '4px', md: 0 },
}}
>
<Typography variant="h5" textAlign="center" sx={{ py: 2 }}>
List of unsolved logs
</Typography>
<LogsLimitButtons pageLimit={pageLimit} setPageLimit={setPageLimit} />
<LogCards pageLimit={pageLimit} />
</Paper>
);
};
And here's general layout of the app:
const LaunchDesktop = () => {
return (
<Grid2 container spacing={1}>
<Grid2
size={{ xs: 12, md: 8 }}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<SosForm />
</Grid2>
<Grid2 size={{ xs: 12, md: 4 }}>
<LogsList />
</Grid2>
</Grid2>
);
};
Share
Improve this question
edited Feb 6 at 12:16
magrega
asked Feb 6 at 11:17
magregamagrega
16810 bronze badges
1 Answer
Reset to default 0Refered to this example on tanstack website and wrapped my LogCard iteration with a div that has a transform
style. I guess under the hood all of the elements are absolutely positined and need to be explicitly set.
const LogCards = ({ pageLimit }: LogCardsProps) => {
const {
data: logs,
fetchNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteLogsQuery(pageLimit);
const { ref, inView } = useInView();
const allLogs = useMemo(() => logs?.pages.flatMap((log) => log.results), [logs]) || [];
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: allLogs.length,
getScrollElement: useCallback(() => parentRef.current, []),
estimateSize: useCallback(() => 300, []),
overscan: 2,
gap: 8,
});
useEffect(() => {
if (inView) fetchNextPage();
}, [fetchNextPage, inView]);
return !isLoading ? (
<Box
ref={parentRef}
sx={{
overflowY: 'scroll',
}}
>
<div
style={{
height: rowVirtualizer.getTotalSize(),
position: 'relative',
}}
>
<div
style={{
transform: `translateY(${rowVirtualizer.getVirtualItems()[0]?.start ?? 0}px)`,
}}
>
{allLogs &&
rowVirtualizer.getVirtualItems().map((log, index) => {
const LogCardData = allLogs[log.index];
if (!LogCardData) return <span key={`no card index ${index}`}>No cards</span>;
return (
<LogCard
key={LogCardData.id}
data-index={log.index}
ref={rowVirtualizer.measureElement}
log={LogCardData}
/>
);
})}
</div>
</div>
<Box sx={{ height: '20px' }} ref={ref}>
{isFetchingNextPage && <LinearProgress sx={{ m: '2px' }} />}
</Box>
</Box>
) : (
<LinearLoader />
);
};