Winterest / Pinterest Grid + Infinite Scroll
How does Pinterest implement its grid?
Inspiration
Here is how Pinterest makes the grid in their website. As a part of the cloning project, I thought it could be better to follow how they make the grid even though there were methods to use CSS.
Basic idea
Remove all images css of positions( position:absolute; left:0; top:0 ) and make grid only use the translateX and Y on css.
Winterest grid (aka Magic grid)
//./pages/Main.js
<WinterestGrid url={API.main} query={query} />
//./components/WinterestGrid.js
const WinterestGrid = ({ url, query }) => {
const [page, setPage] = useState(0);
const { pins, loading, error } = useScrollFetch(url, page, query);
const gridLoader = useRef(null);
const handleObserver = useCallback(entries => {
const target = entries[0];
if (target.isIntersecting) {
setPage(prev => prev + 1);
}
}, []);
//Always refresh the grid loader
useEffect(() => {
const option = {
root: null,
rootMargin: '20px',
threshold: 0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (gridLoader.current) observer.observe(gridLoader.current);
}, [handleObserver]);
return (
<>
<Grid>
{pins && <GridPost pins={pins} />}
{loading && <FetchInform message="그리드 로딩 중" />}
{error && <FetchInform message="에러" />}
</Grid>
<LoaderBox windowHeight={document.body.scrollHeight} ref={gridLoader} />
</>
);
};
//LoaderBox must keep on the bottom of the window.innerHeight every time.
const LoaderBox = styled.div.attrs(props => ({
windowHeight: `${
props.windowHeight === 0 ? window.innerHeight : props.windowHeight
}px`,
}))`
position: absolute;
top: ${props => props.windowHeight};
width: 20px;
height: 20px;
`;
Fetch
//./fetch/useScrollFetch.js
//custom hook for infinite scroll fetch
//How much request the amount of image data from the API.
const LIMIT = 10;
//I added logic to input the random height number of the image_height. This value uses for the height and offsets y of posts on the Winterest Grid.
const giveRandomHeight = pins => {
const result = pins.message.map(pin => {
pin.image_height += randomHeight();
return pin;
});
return result;
};
const randomHeight = () => {
const heightList = [100, 300, 600, 1200];
const newHeight = heightList[Math.floor(Math.random() * heightList.length)];
return newHeight;
};
const useScrollFetch = (url, page, query) => {
const [pins, setPins] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const location = useLocation;
//Empty "pins" whenever location.pathname is changed.
useEffect(() => {
setPins([]);
}, [location.pathname]);
// Updating of page, url, and query refreshes the fetch to happen pagination
useEffect(() => {
getImages(page, url, query);
}, [page, url, query]);
//get data from API with query parameter
const getImages = (page, url, query) => {
const pagenation = `?limit=${LIMIT}&offset=${LIMIT * page}`;
const isQuery = query === undefined;
fetch(url + pagenation + `${isQuery ? '' : query}`, {
headers: { Authorization: API.token },
})
.then(res => res.json())
.then(pinsData => {
setPins(prevPins => {
if (prevPins === []) {
return giveRandomHeight(pinsData);
} else {
return [...prevPins, ...giveRandomHeight(pinsData)];
}
});
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
};
return { pins, loading, error };
};
Things to improve
- Make fetch only where they are
- Improve for responsible page and quantity of the column