skip to Main Content

Situation: I want to make infinite scroll for my project. Currently, I’m using Javascript fetching API but want to apply react-query instead of.

Problem: React query is a form of Hook so can’t be nested inside of useCallback.

Below codes are my current infinite scroller.

function RQInfiniteScrollTable({src}:{src:string}){
    const [page,setPage] = useState(1);
    const [pageData,setPageData]:[pageData:Array<object>,setPageData:any] = useState([]);

    const loadMore = useCallback(async () => {
        const new_page = page + 1;
        setPage(new_page);
        const buffer: Array<object> = [...pageData];
        let moreData:Response|null = await fetch([src,new_page].join("/").trim());
        let moreData_json:object|null = await moreData.json();
        if(moreData_json === null){return;}
        buffer.push(moreData_json);
        setPageData(buffer);
        return ()=>{moreData = null; moreData_json = null;};
    },[pageData]);

    const products:Array<React.ReactNode> = [];
    // @ts-ignore
    pageData.forEach(({title,image,price,category,description},index):void=>{
        products.push(
        <div key={index} className={"infinite-item-card"}>
            <div className={"w-full h-64 relative"}>
                <Image src={image} alt={title} layout={"fill"} objectPosition={"cover"} objectFit={"cover"}/>
            </div>
            <h1>{title}</h1>
            <p>{price}</p>
            <p>{category}</p>
            <p>{description}</p>
        </div>
        );
    });
    return(
    <div className={"w-full grid justify-items-center"}>
        <h1 className={"text-2xl w-fit my-8"}>{"Infinite Scroller"}</h1>
        <div className={"grid"}>{products}</div>
        <InfiniteScroller loadMore={loadMore}/> <-- this node callbacks given `loadMore` function when intersected.
    </div>
    );
}

Question: how to solve this problem? I want to use react-query for better handling, with optimized way. and does query result of useQuery trigger re-render of the component?

2

Answers


  1. Chosen as BEST ANSWER

    I solve this problem! thanks for suggesting above. I created onChange state for dependency for Observer's useCallback

    const queryClient = new QueryClient();
    export default function RQIS({src}:{src:string}){
        return(
        <QueryClientProvider client={queryClient}>
            <_RQIS src={src}/>
        </QueryClientProvider>
        );
    }
    const fetcher = async (endpoint:string,index:number) => {
        try {
            const response = await fetch([endpoint,index].join("/").trim());
            const data = await response.json();
            return data;
        } catch (error) {
            throw new Error('Failed to fetch data');
        }
    };
    function _RQIS({src}:{src:string}){
        //latestIndex는 요청해야 할 물건의 색인입니다.
        const [latestIndex,setLatestIndex] = useState(0);
        const [onChange,setOnChange] = useState(false);
        const queryStackRef:any = useRef([]);
        const {data,isLoading,isError} = useQuery(["data",src,latestIndex],()=>fetcher(src,latestIndex),{
            onSuccess:(data)=>{
                queryStackRef.current.push(<RQIS_ItemCard key={latestIndex} data={data}/>);
                setOnChange(!onChange);
            }
        });
    
        const loadMore = useCallback(async ()=>{
            const new_latestIndex = latestIndex + 1; console.log(latestIndex);
            setLatestIndex(new_latestIndex);
        },[onChange]);
    
        return(
        <div className={""}>
            <div>{queryStackRef.current}</div>
            <InfiniteScroller loadMore={loadMore}/>
        </div>
        );
    }
    function InfiniteScroller({loadMore}:{loadMore:any}){
        const observerRef:any = useRef(null);
        useEffect(()=>{
            const target:HTMLElement|null = document.getElementById("infinite-scroller");
            if(target === null){return;}
            const intersectionHandler = (entries:any)=>{
                entries.forEach((entry:any)=>{
                    if(entry.isIntersecting){loadMore();}
                });
            }
            const options = {root: null, rootMargin:"0px",threshold: 1}
            observerRef.current = new IntersectionObserver(intersectionHandler,options);
            observerRef.current.observe(target);
            return ()=>{observerRef.current.disconnect();};
        },[loadMore]);
        return(<div className={"w-fill h-4 bg-black"} id={"infinite-scroller"}/>);
    }
    function RQIS_ItemCard({data}:{data:any}){
        if(data === undefined){return(<></>);}
        return(
        <div className={"h-40"}>
            <h1>{data.title}</h1>
            <p>{data.price.toString()}</p>
        </div>
        );
    }
    

  2. I’d suggest defining a query that takes the page number as a parameter, and then including that parameter in the query key. Then, whenever you update the page number, react-query will take care of automatically fetching the result. Here’s a mock version I’ve sketched up:

    useGetPaginatedDataQuery.tsx

    export const useGetPaginatedDataQuery = (source: string, pageNumber: number) => {
      return useQuery(["paginatedData", pageNumber], {
        queryFn: async () => {
          const response = await fetch(`${source}/${pageNumber}`);
          return response.json();
        },
        enabled: !!pageNumber && pageNumber >= 0,
      }
    }
    

    RQInfiniteScrollTable.tsx

    function RQInfiniteScrollTable({src}:{src:string}){
        const [page,setPage] = useState(1);
        const pageDataQuery = useGetPaginatedDataQuery(src, page);
    
        const loadMore = () => setPage((page) => page + 1);
    
        // The rest of your component
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search