Skip to content

Commit

Permalink
Merge pull request #139 from str0yka/feat-useInfiniteScroll
Browse files Browse the repository at this point in the history
[feat]: useInfiniteScroll
  • Loading branch information
debabin authored Jul 4, 2024
2 parents 651703d + 19ce04a commit 9867aa8
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/hooks/useInfiniteScroll/useInfiniteScroll.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState } from 'react';

import { useInfiniteScroll } from './useInfiniteScroll';

const Demo = () => {
const [data, setData] = useState([1, 2, 3, 4, 5, 6]);

const ref = useInfiniteScroll<HTMLDivElement>(
() => {
setData((prevData) => {
const length = prevData.length + 1;
return [...prevData, ...new Array(5).fill(null).map((_, i) => length + i)];
});
},
{ distance: 10 }
);

return (
<div
ref={ref}
style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
padding: '16px',
margin: 'auto',
width: '300px',
height: '300px',
overflowY: 'scroll',
background: '#6b72800d',
borderRadius: '4px'
}}
>
{data.map((item) => (
<div
key={item}
style={{
background: '#6b72800d',
borderRadius: '4px',
padding: '12px'
}}
>
{item}
</div>
))}
</div>
);
};

export default Demo;
84 changes: 84 additions & 0 deletions src/hooks/useInfiniteScroll/useInfiniteScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';

export type UseInfiniteScrollTarget = React.RefObject<Element | null> | (() => Element) | Element;

const getElement = (target: UseInfiniteScrollTarget) => {
if (typeof target === 'function') {
return target();
}

if (target instanceof Element) {
return target;
}

return target.current;
};

export interface UseInfiniteScrollOptions {
distance?: number;
direction?: 'top' | 'bottom' | 'left' | 'right';
}

export type UseInfiniteScrollReturn<Target extends UseInfiniteScrollTarget> =
React.RefObject<Target>;

export type UseInfiniteScroll = {
<Target extends UseInfiniteScrollTarget>(
target: Target,
callback: (event: Event) => void,
options?: UseInfiniteScrollOptions
): void;

<Target extends UseInfiniteScrollTarget>(
callback: (event: Event) => void,
options?: UseInfiniteScrollOptions,
target?: never
): UseInfiniteScrollReturn<Target>;
};

export const useInfiniteScroll = ((...params) => {
const target = params[1] instanceof Function ? (params[0] as UseInfiniteScrollTarget) : undefined;
const callback = params[1] instanceof Function ? params[1] : (params[0] as () => void);
const { direction = 'bottom', distance = 10 } =
(params[1] instanceof Function ? params[2] : params[1]) ?? {};

const internalRef = React.useRef<Element>(null);
const internalCallbackRef = React.useRef(callback);

React.useEffect(() => {
internalCallbackRef.current = callback;
}, [callback]);

React.useEffect(() => {
const element = target ? getElement(target) : internalRef.current;

const onLoadMore = (event: Event) => {
if (!element) return;

const { clientHeight, scrollHeight, scrollTop, clientWidth, scrollWidth, scrollLeft } =
element;
const scrollBottom = scrollHeight - (scrollTop + clientHeight);
const scrollRight = scrollWidth - (scrollLeft + clientWidth);

const distances = {
bottom: scrollBottom,
top: scrollTop,
right: scrollRight,
left: scrollLeft
};

if (distances[direction] <= distance) {
internalCallbackRef.current(event);
}
};

element?.addEventListener('scroll', onLoadMore);

return () => {
element?.removeEventListener('scroll', onLoadMore);
};
}, [direction, distance]);

if (target) return;
return internalRef;
}) as UseInfiniteScroll;

0 comments on commit 9867aa8

Please sign in to comment.