The component is not working the way you expect because you are using the same reference for all the items. You can use ref
to store an array of reference or create a component with the list item logic.
If you don't want to render all the items at the same time, you can render a portion (100), and every time the scroll
reaches the end, render 100 more and so on. I recommend
you to use React.memo
to avoid render the item every time the state updates:
PortfolioItem.js
const PortfolioItem = React.memo(({ ix }) => {
const ref = useRef();
const [inViewRef, inView] = useInView({
threshold: 1,
rootMargin: '0px',
});
const setRefs = useCallback(
(node) => {
ref.current = node;
inViewRef(node);
},
[], //--> empty dependencies
);
return ( <GridItem bg={inView?"red.100":"blue.100"} ref={setRefs} _last={{ mb: 4 }} >
<Center border={1} borderColor="gray.100" borderStyle="solid" h={16} w="100%">
Item {ix}
</Center>
</GridItem>)
});
PortfolioList.js
export const PortfolioList = ({
title,
count = 100
}: PortfolioListProps) => {
const ref = useRef(null);
const items = [...Array(1000)];
const [index, setIndex] = useState(count);
useEffect(()=> {
const grid = ref.current;
function onScroll(){
if(grid.offsetHeight + grid.scrollTop >= grid.scrollHeight) {
setIndex(prev => prev+count);
}
}
grid.addEventListener("scroll", onScroll);
return ()=> {
grid.removeEventListener("scroll", onScroll);
}
}, []);
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={ref}
>
{items.slice(0,index).map((pt,ix) => (
<PortfolioItem ix={ix} key={`Postfolio__item-${ix}`}/>
))
}
</Grid>
</Box>
);
};
Working example