Skip to content

Commit

Permalink
feat(infinite-scroll): change props contract
Browse files Browse the repository at this point in the history
  • Loading branch information
StewartJingga committed Apr 23, 2024
1 parent 9056b4c commit e297069
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 203 deletions.
50 changes: 5 additions & 45 deletions packages/apsara-ui/src/InfiniteScroll/InfiniteScroll.styles.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,8 @@
import styled from "styled-components";

export const UserCard = styled.div`
margin-bottom: 16px;
border-radius: 2px;
box-shadow: rgb(179 179 179 / 31%) 0px 1px 5px;
.title {
margin: 10px;
color: #4d85f4;
font-size: 16px;
}
.description {
margin-top: 20px;
word-break: break-all;
}
.body {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0px 20px 12px 20px;
> .body-left {
margin-top: 16px;
flex: 1;
}
> .body-right {
width: 240px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
> div {
display: flex;
align-items: flex-end;
flex-direction: column;
}
.tag_content {
text-transform: uppercase;
}
}
}
export const InfiniteScrollWrapper = styled.div`
position: relative;
height: calc(100% - 0px);
height: calc(100% - 200px);
overflow-y: scroll;
`;
127 changes: 55 additions & 72 deletions packages/apsara-ui/src/InfiniteScroll/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,81 @@
import { debounce } from "lodash";
import React, { useState, useEffect, useCallback, useRef } from "react";
import React, { useEffect, useRef } from "react";

import { InfiniteScrollWrapper } from "./InfiniteScroll.styles";

interface InfiniteScrollProps<T> {
fetchMoreData: (page: number, pageSize: number, filters: any) => Promise<T[]>;
contentRef?: React.RefObject<HTMLDivElement>;
page: number;
pageSize?: number;
filters?: any;
threshold?: number;
renderItem: (item: T) => React.ReactNode;
onBottomScroll: () => void;
items: T[];
style?: React.CSSProperties;
isLoading?: boolean;
noMoreData?: boolean;
containerRef?: React.RefObject<HTMLElement>;
threshold?: number;
loadingComponent?: React.ReactNode;
noMoreDataComponent?: React.ReactNode;
}

const InfiniteScroll: React.FC<InfiniteScrollProps<any>> = ({
fetchMoreData,
contentRef,
page,
pageSize = 10,
threshold = 0,
filters,
const InfiniteScroll = <T,>({
renderItem,
onBottomScroll,
items,
style,
isLoading,
noMoreData,
containerRef,
threshold = 0,
loadingComponent,
noMoreDataComponent,
}) => {
const [data, setData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMoreData, setHasMoreData] = useState(true);

const defaultContentRef = useRef<HTMLDivElement>(null);
const refToUse = contentRef || defaultContentRef;
useEffect(() => {
setData([]);
setHasMoreData(true);
}, [filters]);

const isBottom = (ref: React.RefObject<HTMLDivElement>) => {
if (!ref.current) {
return false;
}

const { scrollTop, scrollHeight, clientHeight } = ref.current;

return scrollTop + clientHeight >= scrollHeight - threshold;
};

const fetchMore = useCallback(
debounce(async () => {
setIsLoading(true);
try {
const newData = await fetchMoreData(page, pageSize, filters);
setData((prevData) => prevData.concat(newData));
setHasMoreData(newData.length < pageSize ? false : true);
} finally {
setIsLoading(false);
}
}, 200),
[page, filters, isLoading],
);
...props
}: InfiniteScrollProps<T>) => {
const defaultContainerRef = useRef<HTMLDivElement>(null);
const containerElem = containerRef ? containerRef.current : defaultContainerRef.current;

useEffect(() => {
if (page === 1 && !data.length && !isLoading && filters !== undefined) {
fetchMore();
}
if (!containerElem) return;

const onScroll = () => {
if (!isLoading && hasMoreData && isBottom(refToUse)) {
fetchMore();
if (isBottom(containerElem, threshold)) {
onBottomScroll();
}
};

const currentContentRef = refToUse.current;
if (currentContentRef) {
currentContentRef.addEventListener("scroll", onScroll);
}

containerElem.addEventListener("scroll", onScroll);

return () => {
if (currentContentRef) {
currentContentRef.removeEventListener("scroll", onScroll);
}
containerElem.removeEventListener("scroll", onScroll);
};
}, [refToUse, contentRef, defaultContentRef, fetchMore, filters, data, hasMoreData, isBottom, isLoading, page]);
}, [containerElem, onBottomScroll, threshold]);

const loadingComp = loadingComponent || <DefaultLoading />;

return (
<div
className="infinite-list"
style={!contentRef ? { height: "100%", overflowY: "auto" } : {}}
ref={defaultContentRef}
<InfiniteScrollWrapper
className="apsara-infinite-scroll-wrapper"
style={style}
ref={defaultContainerRef}
{...props}
>
{data.map(renderItem)}
{isLoading ? loadingComponent : null}
{!hasMoreData && noMoreDataComponent}
</div>
{items?.map(renderItem)}
{isLoading && loadingComp}
{!noMoreData && noMoreDataComponent}
</InfiniteScrollWrapper>
);
};

const DefaultLoading = () => <div>Loading...</div>;

const isBottom = (elem: HTMLElement, threshold: number): boolean => {
if (!elem) return false;

const { scrollTop, scrollHeight, clientHeight } = elem;

const a = scrollTop + clientHeight;
const b = scrollHeight - threshold;

console.log(a);
console.log(b);
return a >= b;
};

export default InfiniteScroll;
Loading

0 comments on commit e297069

Please sign in to comment.