使用GraphQL和IntersectionObserver的无限滚动组件



我对React没有什么经验,我正在尝试使用GraphQL和IntersectionObserver进行无限滚动,所有这些都可以,但我得到的结果是我要求的两倍。我想它来自双重渲染,一个来自@apolo/clientfetchMore,另一个来自IntersectionObserver钩子,但我不太理解它,也不知道如何修复它。

IntersectionObserver钩子:

import { useCallback, useRef, useState } from "react";
export default function useIntersection(options?: IntersectionObserverInit): IntersectionHook {
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const observer = useRef<IntersectionObserver>();
const target = useRef<Element | null>(null);
const setSentinel = useCallback<IntersectionHook[0]>(
(sentinel) => {
if (!observer.current) {
observer.current = new IntersectionObserver((entries) => setEntry(entries[0]), options);
}
if (target.current) {
observer.current.unobserve(target.current);
}
target.current = sentinel;
if (target.current) {
observer.current.observe(target.current);
}
},
[options],
);
return [setSentinel, entry];
}
type IntersectionHook = [(sentinel: Element | null) => void, IntersectionObserverEntry | undefined];

使用挂钩的组件:

import { Fragment, ReactElement, ReactNode, useEffect } from "react";
import useIntersection from "../hooks/intersectionHook";
export default function InfiniteComponent(props: InfiniteComponentProps): ReactElement {
const [setSentinel, entry] = useIntersection();
useEffect(() => {
if (entry?.isIntersecting) {
props.onNext();
}
}, [props, entry]);
return (
<Fragment>
{props.children}
{props.next && <div ref={setSentinel}></div>}
</Fragment>
);
}
export interface InfiniteComponentProps {
next?: boolean;
onNext: () => Promise<unknown>;
children: ReactNode;
}

使用组件的页面:

export default function IndexArtist(): ReactElement | null {
const { loading, error, data, fetchMore } = useQuery<IndexArtistData, IndexArtistVars>(INDEX_ARTIST, {
variables: { limit: 3 },
});
if (loading) {
return null;
}
if (error) {
return <Redirect to="/error" />;
}
if (!data?.artists.nodes.length) {
return <Redirect to="/error" />;
}
return (
<BoxElement $variant="page">
<InfiniteComponent
next={data.artists.page.next}
onNext={async () => fetchMore({ variables: { cursor: data.artists.page.cursor } })}
>
{/* The code to render */}
</InfiniteComponent>
</BoxElement>

使用上面的代码,我请求3个结果,在加载页面时得到3个结果。但当我向下滚动到IntersectionObserver钩子的sentinel以获取更多结果时,我得到6个结果(首先我得到3个,但不久后,几毫秒后,我得到了其他3个结果(。

我仍然愿意接受建议。下一个解决方案似乎并不更优雅,但它是我唯一找到的。

使用挂钩的组件:

import { Fragment, ReactElement, ReactNode, useEffect, useRef, useState } from "react";
import useIntersection from "../hooks/intersectionHook";
export default function InfiniteComponent(props: InfiniteComponentProps): ReactElement {
const [wait, setWait] = useState(false);
const ready = useRef(!wait);
const [entry, setSentinel] = useIntersection();
useEffect(() => {
ready.current = !wait;
}, [wait]);
useEffect(() => {
const fetchMore = async (): Promise<void> => {
setWait(true);
await props.onMore();
setWait(false);
};
if (ready.current && entry?.isIntersecting) {
fetchMore();
}
}, [entry, props]);
return (
<Fragment>
{props.children}
{props.next && !wait && <div ref={setSentinel}></div>}
</Fragment>
);
}
export interface InfiniteComponentProps {
next?: boolean;
onMore: () => Promise<unknown>;
children: ReactNode;
}

最新更新