I am trying to use the intersection observer API to conditionally display items in a CSS grid when the user starts scrolling, but it seems to go into an infinite rendering loop. Here is my code.
Here is the link to my live code on StackBlitz
Also what I'm trying to achieve is not render too many items on the screen when I can avoid it. I'm not sure if display: none
actually makes the browser work less. If this is not the correct way, please let me know.
Thanks for reading my question. Any help is highly appreciated.
You have 1000 GridItem
components which are all getting the same callback ref setRefs
. They all receive the same value of inView
even though we know that at any given time some are in view and others are not. What ends up happening is that each items overwrites the previously set ref such that all 1000 items receive a boolean inView
that represents whether the last item in the list is in view -- not whether it is itself in view.
useInView
for Each ElementIn order to know whether each individual component is in view or not, we need to use the useInView
hook separately for each element in the list. We can move the code for each item into its own component. We need to pass this component its number ix
and the options for the useInView
hook (we could also just pass down the root ref and create the options
object here).
import { Box, Flex, Text, useColorModeValue as mode, Divider, Grid, GridItem, Center } from '@chakra-ui/react';
import { useInView, IntersectionOptions } from 'react-intersection-observer';
import React, { useRef } from 'react';
interface ItemProps {
ix: number;
inViewOptions: IntersectionOptions;
}
export const ListItem = ({ix, inViewOptions}: ItemProps) => {
const {ref, inView}= useInView(inViewOptions);
return (
<GridItem bg={inView?"red.100":"blue.100"} ref={ref} _last={{ mb: 4 }} key={ix}>
<Center border={1} borderColor="gray.100" borderStyle="solid" h={16} w="100%">
Item {ix}
</Center>
</GridItem>
)
}
export type PortfolioListProps = {
title: string;
};
export const PortfolioList = ({
title,
}: PortfolioListProps) => {
const parRef = useRef(null);
return (
<Box
w="100%"
mx="auto"
rounded={{ md: `lg` }}
bg={mode(`white`, `gray.700`)}
shadow="md"
overflow="hidden"
>
<Flex align="center" justify="space-between" px="6" py="4">
<Text as="h3" fontWeight="bold" fontSize="xl">
{title}
</Text>
</Flex>
<Divider />
<Grid
p={4}
gap={4}
templateColumns="1fr 1fr 1fr 1fr"
templateRows="min-content"
maxH="500px"
minH="500px"
overflowY="auto"
id="list"
ref={parRef}
>
{[...Array(1000)].map((pt,ix) => (
<ListItem ix={ix} key={ix} inViewOptions={{
threshold: 1,
rootMargin: '0px',
root: parRef?.current,
}}/>
))}
</Grid>
</Box>
);
};