Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve infinite scroll issue in Resource page #9267

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

NikhilA8606
Copy link

@NikhilA8606 NikhilA8606 commented Dec 2, 2024

Describe the bug

The resource page has an infinite scroll issue where content keeps loading repeatedly, causing poor performance and a bad user experience. The stopping condition for the scroll is not working correctly, leading to continuous or unnecessary data fetching.

Screen recording of the error is given below

Screen.Recording.2024-12-02.094908.mp4

Screen recording of the page after resolving the error

Screen.Recording.2024-12-02.115614.mp4

Summary by CodeRabbit

  • New Features

    • Enhanced infinite scrolling and pagination for Kanban and Resource boards.
    • Introduced a new mechanism for fetching more items using IntersectionObserver.
    • Added lazy loading support for components with specific prop types.
  • Bug Fixes

    • Improved error handling for drag-and-drop operations in the Shifting Board.
  • Documentation

    • Updated type definitions for various components to enhance clarity and type safety.

@NikhilA8606 NikhilA8606 requested a review from a team as a code owner December 2, 2024 07:22
Copy link
Contributor

coderabbitai bot commented Dec 2, 2024

Walkthrough

This pull request introduces a comprehensive update to the pagination and infinite scrolling mechanism across multiple components. The changes focus on implementing a new useInfiniteQuery hook that manages data fetching, deduplication, and pagination state. The modifications span several files, including Kanban/Board.tsx, Resource/ResourceBoard.tsx, Shifting/ShiftingBoard.tsx, and a new utility hook useInfiniteQuery.ts. The implementation aims to improve type safety, simplify component logic, and provide a more robust approach to handling paginated API responses.

Changes

File Change Summary
src/components/Kanban/Board.tsx - Exported KanbanBoardProps interface
- Replaced pagination logic with useTanStackQueryInstead hook
- Introduced IntersectionObserver for fetching items
src/components/Resource/ResourceBoard.tsx - Updated lazy loading with lazyWithProps
- Improved type safety for KanbanBoard component
- Modified boardFilter initialization to use resourceStatusOptions
src/components/Shifting/ShiftingBoard.tsx - Added lazyWithProps for type-safe lazy loading
- Enhanced drag-and-drop error handling
- Explicitly typed function parameters
src/Utils/request/useInfiniteQuery.ts - Introduced useInfiniteQuery hook
- Added InfiniteQueryOptions interface
- Implemented item deduplication mechanism

Assessment against linked issues

Objective Addressed Explanation
Fix repeated rendering of resource cards [#9334]
Implement infinite scrolling [#9334]

Possibly related PRs

  • Util function for cleaner usage of TanStack useQuery #9395: The changes in this PR involve the introduction of a utility function for cleaner usage of TanStack's useQuery, which relates to the modifications made in the main PR that also involve the useTanStackQueryInstead hook for managing data fetching and pagination state in the KanbanBoard component.

Suggested Labels

needs testing, needs review, P1

Suggested Reviewers

  • rithviknishad

Poem

🐰 Scrolling through data, smooth and light,
Infinite queries take their flight.
No more duplicates in sight,
Pagination dances with delight.
Code flows free, everything's right! 🚀


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Dec 2, 2024

Deploy Preview for care-ohc ready!

Name Link
🔨 Latest commit b9c0bc3
🔍 Latest deploy log https://app.netlify.com/sites/care-ohc/deploys/676123b32fa8db0007583a26
😎 Deploy Preview https://deploy-preview-9267--care-ohc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
src/components/Kanban/Board.tsx (3)

97-100: Add error handling for refresh operation

While the reset logic is correct, consider adding error handling to manage potential failures during refresh operations.

 if (refresh) {
+  try {
     setPages([]);
     setOffset(0);
+  } catch (error) {
+    console.error('Failed to refresh kanban board:', error);
+    // Consider adding error state handling
+  }
 }

125-138: Consider making scroll threshold configurable

The scroll threshold of 100 pixels should be made configurable to accommodate different use cases and screen sizes.

+interface KanbanSectionProps<T> extends Omit<KanbanBoardProps<T>, "sections" | "onDragEnd"> {
+  section: KanbanBoardProps<T>["sections"][number];
+  boardRef: RefObject<HTMLDivElement>;
+  scrollThreshold?: number;  // New prop
+}

 const onBoardReachEnd = () => {
   if (
     !sectionRef.current ||
     !props.boardRef.current ||
     fetchingNextPage ||
     !hasMore
   )
     return;

   const scrollTop = props.boardRef.current.scrollTop;
   const visibleHeight = props.boardRef.current.offsetHeight;
   const sectionHeight = sectionRef.current.offsetHeight;

-  if (scrollTop + visibleHeight >= sectionHeight - 100) {
+  const threshold = props.scrollThreshold ?? 100;
+  if (scrollTop + visibleHeight >= sectionHeight - threshold) {
     fetchNextPage();
   }
 };

177-190: Consider adding item-level loading states

While there's a loading state for the entire section, consider adding loading states for individual items to improve user experience during drag operations.

 {items.map((item, i) => (
   <Draggable draggableId={item.id} key={i} index={i}>
     {(provided) => (
       <div
         ref={provided.innerRef}
         {...provided.draggableProps}
         {...provided.dragHandleProps}
-        className="mx-2 mt-2 w-[284px] rounded-lg border border-secondary-300 bg-white"
+        className={`mx-2 mt-2 w-[284px] rounded-lg border border-secondary-300 bg-white ${
+          item.isLoading ? 'opacity-50' : ''
+        }`}
       >
         {props.itemRender(item)}
       </div>
     )}
   </Draggable>
 ))}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f76c625 and 83e9154.

📒 Files selected for processing (2)
  • src/components/Kanban/Board.tsx (6 hunks)
  • tsconfig.json (1 hunks)
🔇 Additional comments (3)
tsconfig.json (1)

18-18: LGTM: Node types addition supports debounce implementation

The addition of Node.js types is necessary to support the NodeJS.Timeout type used in the debounce implementation.

src/components/Kanban/Board.tsx (2)

60-60: LGTM: Improved scrollbar behavior

The change from overflow-scroll to overflow-auto is an improvement as it shows scrollbars only when necessary, providing a better user experience.


143-147: LGTM: Effective scroll performance optimization

The implementation of debounced scroll handling effectively prevents excessive API calls during rapid scrolling, addressing the infinite scroll issue mentioned in the PR objectives.

src/components/Kanban/Board.tsx Outdated Show resolved Hide resolved
@JavidSumra
Copy link
Contributor

JavidSumra commented Dec 2, 2024

Hey @NikhilA8606 I'm working on building useInfiniteQuery which would resolve this issue.
PR #9190

@github-actions github-actions bot added needs-triage question Further information is requested labels Dec 2, 2024
@rithviknishad
Copy link
Member

Also useDebounce hook is being implemented by @SwanandBhuskute which can be reused here instead of implementing it.

@rithviknishad rithviknishad added waiting for related PR a co-related detail PR is under construction and removed question Further information is requested needs testing needs review needs-triage labels Dec 2, 2024
tsconfig.json Outdated
@@ -15,7 +15,7 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": ["vite/client", "vite-plugin-pwa/client"],
"types": ["vite/client", "vite-plugin-pwa/client","node"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this change intended?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate more? state the reason and what it solves? Just curious on what this does.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used to ensure TypeScript recognizes Node.js globals and core modules, preventing related errors.And it solves issues related to unrecognized Node.js globals (like process), core modules (like fs and path), and environment variables in TypeScript.
Example of the error which i saw are " Cannot find name 'process', __dirname, or require",
Cannot find module 'fs' or its corresponding type declarations etc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

care_fe is strictly client-side based and doesn't / shouldn't be using any nodejs specific globals or modules directly.

cc: @sainak

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing that out! I did use ChatGPT to explore a solution for the issue, which led to including "node" in the types array

Copy link
Author

@NikhilA8606 NikhilA8606 Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried resolving the issue without including Node.js, but I encountered related errors

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats why i asked help for chatgpt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what error exactly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error occurred before I implemented the useDebounce.ts solution, which prompted me to include "node" in the types array as a temporary fix. After implementing the useDebounce logic, I overlooked removing "node" from the configuration. I’ve rechecked the issue now, and it’s working perfectly without needing Node.js. Apologies for the oversight, and thank you for pointing it out!

@NikhilA8606
Copy link
Author

NikhilA8606 commented Dec 3, 2024

Also useDebounce hook is being implemented by @SwanandBhuskute which can be reused here instead of implementing it.

Hey, i walked through the pull requests of @SwanandBhuskute but i can't see this hook in there.
Could you please share a link to the pull request or branch where the useDebounce hook is implemented? I’d like to integrate it here as suggested. Thanks!

@github-actions github-actions bot added needs-triage question Further information is requested labels Dec 3, 2024
@rithviknishad
Copy link
Member

The link attached takes right to the useDebounce hook implementation

@NikhilA8606
Copy link
Author

The link attached takes right to the useDebounce hook implementation

Yeah i got it

@nihal467 nihal467 removed question Further information is requested needs-triage labels Dec 4, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
src/hooks/useDebounce.ts (1)

3-6: Consider strengthening type safety

While the generic type parameter works, it could be more restrictive to prevent potential misuse.

-export default function useDebounce<T extends any[]>(
+export default function useDebounce<T extends unknown[]>(
   callback: (...args: T) => void,
   delay: number,
 )
src/components/Resource/ResourceBoard.tsx (1)

28-33: Consider moving helper function to utils

The lazyWithProps helper is a generic utility function that could be useful across the application.

Consider moving it to a shared utilities file, e.g., src/Utils/lazy.ts.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 83e9154 and 97119a0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json (2 hunks)
  • src/components/Kanban/Board.tsx (8 hunks)
  • src/components/Resource/ResourceBoard.tsx (7 hunks)
  • src/components/Shifting/ShiftingBoard.tsx (6 hunks)
  • src/hooks/useDebounce.ts (1 hunks)
🔇 Additional comments (9)
src/hooks/useDebounce.ts (1)

20-27: 🛠️ Refactor suggestion

Potential race condition with multiple rapid invocations

The current implementation might miss the latest arguments if the function is called multiple times rapidly, as it uses closure over the args parameter.

Consider testing the behavior with this script:

Consider storing the latest arguments in a ref to ensure the most recent call is processed:

 const debouncedCallback = (...args: T) => {
   if (timeoutRef.current) {
     clearTimeout(timeoutRef.current);
   }
+  const argsRef = args; // Capture latest arguments
   timeoutRef.current = setTimeout(() => {
-    callbackRef.current(...args);
+    callbackRef.current(...argsRef);
   }, delay);
 };
src/components/Resource/ResourceBoard.tsx (1)

Line range hint 113-155: Verify infinite scroll fix implementation

The PR's objective is to fix the infinite scroll issue, but the changes in this component don't directly address the scroll behavior. Ensure that the infinite scroll fix is properly implemented in the KanbanBoard component.

Let's verify the KanbanBoard implementation:

✅ Verification successful

Let me gather more information about the KanbanBoard component specifically.


Let me try to locate the KanbanBoard component with a different approach.


Let me check the Kanban Board implementation and its scroll-related functionality.


Infinite scroll is properly implemented in the KanbanBoard component

The KanbanBoard component has a robust infinite scroll implementation:

  • Uses scroll event listener with debouncing to detect when user reaches near the bottom
  • Implements pagination with offset and limit
  • Handles loading states and displays loading placeholders
  • Maintains separate page states for each section
  • Properly cleans up event listeners

The implementation in src/components/Kanban/Board.tsx shows that the infinite scroll functionality is working as expected, with proper handling of:

  • Scroll detection (debouncedScroll function)
  • Page fetching (fetchNextPage function)
  • Loading states (fetchingNextPage)
  • Empty states
  • Scroll cleanup

The review comment's concern about infinite scroll issues has been addressed in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check KanbanBoard component for scroll-related changes
ast-grep --pattern 'useInfiniteQuery($_)' 
rg -A 10 "onScroll|InfiniteScroll|useInfinite"

Length of output: 4444


Script:

#!/bin/bash
# Check KanbanBoard component implementation
fd KanbanBoard.tsx --exec cat {}
rg -A 10 "fetchMore|useInfinite|onScroll" src/components/Common/KanbanBoard.tsx

Length of output: 196


Script:

#!/bin/bash
# Find KanbanBoard component location and its content
fd -e tsx -e ts -e js Kanban
rg -l "KanbanBoard"

Length of output: 166


Script:

#!/bin/bash
# Check the Kanban board implementation
cat src/components/Kanban/Board.tsx
# Look for infinite scroll or query-related implementations in the component
rg -A 10 "useQuery|useInfinite|onScroll|fetchMore" src/components/Kanban/Board.tsx

Length of output: 6901

src/components/Kanban/Board.tsx (4)

Line range hint 18-35: LGTM! Well-structured type definitions.

The exported interface and type alias improve reusability and type safety across the codebase.


64-64: Good choice using overflow-auto.

Using overflow-auto instead of overflow-scroll is more user-friendly as it only shows scrollbars when needed.


101-115: Improved pagination reliability and error handling.

Good improvements:

  1. Proper reset of pagination state on refresh
  2. Added error handling for API response
  3. Consistent query parameter naming

128-150: 🛠️ Refactor suggestion

Consider cleaning up the debounced function.

While the scroll handling implementation is good, consider cleaning up the debounced function to prevent potential memory leaks.

This was previously suggested in an earlier review. The recommendation to enhance the debounce utility and its cleanup remains valid.

src/components/Shifting/ShiftingBoard.tsx (3)

34-42: Well-implemented lazy loading with proper type safety.

The lazyWithProps utility function is a good abstraction that ensures type safety for lazy-loaded components.


177-186: Good defensive programming with proper type safety.

The null check for result.destination prevents potential runtime errors, and the type annotation improves code clarity.


Line range hint 167-192: LGTM! Improved type safety with explicit annotations.

The explicit type annotations in fetchOptions and itemRender props improve code clarity and help catch potential type-related errors early.

package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
src/components/Resource/ResourceBoard.tsx Show resolved Hide resolved
Copy link
Member

@rithviknishad rithviknishad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's switch to using useInfiniteQuery hook as implemented in #9190 similar to how useDebounce was copied over. Do make sure changes its copied exactly as is to avoid merge conflicts.

Comment on lines 35 to 36
export type KanbanBoardType<T extends { id: string }> = KanbanBoardProps<T>;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's going to be exactly same as KanbanBoardProps, why not just re-use it?

Suggested change
export type KanbanBoardType<T extends { id: string }> = KanbanBoardProps<T>;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's going to be exactly same as KanbanBoardProps, why not just re-use it?

Thank you for the suggestion. You're absolutely right that KanbanBoardType is redundant since it's exactly the same as KanbanBoardProps. I'll remove KanbanBoardType and directly reuse KanbanBoardProps to keep the code clean and consistent.

@rithviknishad rithviknishad added changes required and removed waiting for related PR a co-related detail PR is under construction labels Dec 9, 2024
@rithviknishad rithviknishad linked an issue Dec 9, 2024 that may be closed by this pull request
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
src/components/Kanban/Board.tsx (2)

126-142: Consider parameterizing scroll threshold

The scroll threshold of 100 pixels should be a named constant or configurable parameter for better maintainability.

Consider this change:

+  const SCROLL_THRESHOLD = 100; // pixels before bottom to trigger next page
   const debouncedScroll = useDebounce(() => {
     if (
       !sectionRef.current ||
       !props.boardRef.current ||
       fetchingNextPage ||
       !hasMore
     )
       return;

     const scrollTop = props.boardRef.current.scrollTop;
     const visibleHeight = props.boardRef.current.offsetHeight;
     const sectionHeight = sectionRef.current.offsetHeight;

-    if (scrollTop + visibleHeight >= sectionHeight - 100) {
+    if (scrollTop + visibleHeight >= sectionHeight - SCROLL_THRESHOLD) {
       fetchNextPage();
     }
   }, 200);

144-148: Prevent potential memory leaks

The cleanup could be enhanced to handle in-flight requests when the component unmounts.

Consider adding an abort controller:

+  const abortController = useRef<AbortController>();
+
   useEffect(() => {
+    abortController.current = new AbortController();
     props.boardRef.current?.addEventListener("scroll", debouncedScroll);
-    return () =>
+    return () => {
       props.boardRef.current?.removeEventListener("scroll", debouncedScroll);
+      abortController.current?.abort();
+    }
   }, [fetchingNextPage, hasMore, props.boardRef, debouncedScroll]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3a557 and 669bdc9.

📒 Files selected for processing (1)
  • src/components/Kanban/Board.tsx (7 hunks)
🔇 Additional comments (3)
src/components/Kanban/Board.tsx (3)

12-18: LGTM: Clean interface export and hook import

The export of KanbanBoardProps interface and import of useDebounce hook are well-structured changes that support the infinite scroll fix.


177-190: LGTM: Clean rendering implementation

The rendering logic for draggable items is well-implemented with proper props handling and clean markup.


12-12: Verify useDebounce hook integration

Let's verify that the useDebounce hook is properly implemented and available.

✅ Verification successful

useDebounce hook is properly implemented and ready to use

The hook implementation in src/hooks/useDebounce.ts is correct and includes:

  • Proper TypeScript generics for type safety
  • Correct usage of useRef for callback and timeout references
  • Proper cleanup in useEffect
  • Memory leak prevention by clearing timeouts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify useDebounce hook implementation
# Check if the hook exists and is properly exported
rg -l "export.*function useDebounce|export.*const useDebounce" "src/hooks/"

# Check for any other usages that might need to be updated
rg "useDebounce" --type ts --type tsx

Length of output: 166


Script:

#!/bin/bash
# Let's verify with more precise commands
# Check the implementation of useDebounce
cat src/hooks/useDebounce.ts

# Search for usages with proper file type options
rg "useDebounce" -t ts -t typescript

Length of output: 817

Comment on lines 99 to 113
if (refresh) {
setPages([]);
setOffset(0);
}
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: { ...options.options?.query, offsetToUse, limit: defaultLimit },
query: {
...options.options?.query,
offset: offsetToUse,
limit: defaultLimit,
},
});
if (res.error) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and type safety

While the refresh logic is good, there are two areas for improvement:

  1. Error case should reset fetchingNextPage state
  2. 'as any' type assertions could be replaced with proper types

Consider applying these changes:

-    if (res.error) return;
+    if (res.error) {
+      setFetchingNextPage(false);
+      return;
+    }
+    
+    interface PaginatedResponse {
+      results: T[];
+      next: string | null;
+      count: number;
+    }
+    
     const newPages = refresh ? [] : [...pages];
     const page = Math.floor(offsetToUse / defaultLimit);
-    newPages[page] = (res.data as any).results;
+    const data = res.data as PaginatedResponse;
+    newPages[page] = data.results;
-    setHasMore(!!(res.data as any)?.next);
-    setTotalCount((res.data as any)?.count);
+    setHasMore(!!data.next);
+    setTotalCount(data.count);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (refresh) {
setPages([]);
setOffset(0);
}
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: { ...options.options?.query, offsetToUse, limit: defaultLimit },
query: {
...options.options?.query,
offset: offsetToUse,
limit: defaultLimit,
},
});
if (res.error) return;
if (refresh) {
setPages([]);
setOffset(0);
}
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: {
...options.options?.query,
offset: offsetToUse,
limit: defaultLimit,
},
});
if (res.error) {
setFetchingNextPage(false);
return;
}
interface PaginatedResponse {
results: T[];
next: string | null;
count: number;
}
const newPages = refresh ? [] : [...pages];
const page = Math.floor(offsetToUse / defaultLimit);
const data = res.data as PaginatedResponse;
newPages[page] = data.results;
setHasMore(!!data.next);
setTotalCount(data.count);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (1)
src/components/Kanban/Board.tsx (1)

86-92: Optimize by Caching fetchOptions Result

section.fetchOptions(section.id) is called twice, which can lead to unnecessary computations if fetchOptions is an expensive operation.

Store the result of fetchOptions in a variable to avoid redundant calls:

+ const fetchOptions = section.fetchOptions(section.id);

const { items, loading, fetchNextPage, hasMore } = useInfiniteQuery<T>(
-   section.fetchOptions(section.id).route,
+   fetchOptions.route,
  {
-    ...section.fetchOptions(section.id).options,
+    ...fetchOptions.options,
    deduplicateBy: (item) => item.id,
  },
);

This improves performance by eliminating duplicate function calls.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 669bdc9 and d3b8102.

📒 Files selected for processing (2)
  • src/Utils/request/useInfiniteQuery.ts (1 hunks)
  • src/components/Kanban/Board.tsx (4 hunks)

src/Utils/request/useInfiniteQuery.ts Outdated Show resolved Hide resolved
Comment on lines +21 to +27
const { refetch, loading, ...queryResponse } = useQuery(route, {
...options,
query: {
...(options?.query ?? {}),
offset,
},
onResponse: ({ data }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential Overwrite of onResponse and query Options

By spreading ...options and then overriding query and onResponse, any query or onResponse properties provided in the options object will be overwritten. This could lead to unintended behavior if the caller supplies their own onResponse or query parameters.

Consider merging the query parameters and chaining the onResponse callbacks to preserve the functionality provided by the caller:

const { refetch, loading, ...queryResponse } = useQuery(route, {
  ...options,
  query: {
    ...(options?.query ?? {}),
    offset,
  },
- onResponse: ({ data }) => {
+ onResponse: (response) => {
+   options?.onResponse?.(response);
    if (!response.data) return;
-   if (!data) return;
-   const allItems = items.concat(data.results);
+   const allItems = items.concat(response.data.results);

    const deduplicatedItems = options?.deduplicateBy
      ? Array.from(
          allItems
            .reduce((map, item) => {
              const key = options.deduplicateBy(item);
              return map.set(key, item);
            }, new Map<string | number, TItem>())
            .values(),
        )
      : allItems;

    setItems(deduplicatedItems);
    setTotalCount(response.data.count);
  },
});

This ensures that any onResponse logic provided in options is still executed.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 94 to 104
const debouncedScroll = useDebounce(() => {
if (!props.boardRef.current || loading || !hasMore) return;

const fetchNextPage = async (refresh: boolean = false) => {
if (!refresh && (fetchingNextPage || !hasMore)) return;
if (refresh) setPages([]);
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: { ...options.options?.query, offsetToUse, limit: defaultLimit },
});
const newPages = refresh ? [] : [...pages];
const page = Math.floor(offsetToUse / defaultLimit);
if (res.error) return;
newPages[page] = (res.data as any).results;
setPages(newPages);
setHasMore(!!(res.data as any)?.next);
setTotalCount((res.data as any)?.count);
setOffset(offsetToUse + defaultLimit);
setFetchingNextPage(false);
};
const scrollTop = props.boardRef.current.scrollTop;
const visibleHeight = props.boardRef.current.offsetHeight;
const sectionHeight = props.boardRef.current.scrollHeight;

const items = pages.flat();
if (scrollTop + visibleHeight >= sectionHeight - 100) {
fetchNextPage();
}
}, 200);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure Stability of debouncedScroll to Prevent Excessive Event Listeners

The debouncedScroll function may change on every render because useDebounce might return a new function reference each time. This causes the useEffect hook to remove and add the event listener on every render, which can degrade performance.

Modify the useDebounce hook to return a stable function reference across renders. You can achieve this by utilizing useRef internally within useDebounce:

// Inside useDebounce.ts
import { useCallback, useRef } from "react";

export default function useDebounce<T extends (...args: any[]) => void>(
  fn: T,
  delay: number,
  deps: any[] = []
): T {
  const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
  const memoizedFn = useCallback(fn, deps);

  return useCallback(
    (...args: Parameters<T>) => {
      if (timeoutRef.current) clearTimeout(timeoutRef.current);
      timeoutRef.current = setTimeout(() => {
        memoizedFn(...args);
      }, delay);
    },
    [delay, memoizedFn]
  ) as T;
}

Then, update the usage in your component:

const debouncedScroll = useDebounce(
  () => {
    if (!props.boardRef.current || loading || !hasMore) return;

    const scrollTop = props.boardRef.current.scrollTop;
    const visibleHeight = props.boardRef.current.offsetHeight;
    const sectionHeight = props.boardRef.current.scrollHeight;

    if (scrollTop + visibleHeight >= sectionHeight - 100) {
      fetchNextPage();
    }
  },
  200,
+ [loading, hasMore]
);

This ensures that debouncedScroll remains stable unless loading or hasMore changes.

Comment on lines 94 to 110
const debouncedScroll = useDebounce(() => {
if (!props.boardRef.current || loading || !hasMore) return;

const fetchNextPage = async (refresh: boolean = false) => {
if (!refresh && (fetchingNextPage || !hasMore)) return;
if (refresh) setPages([]);
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: { ...options.options?.query, offsetToUse, limit: defaultLimit },
});
const newPages = refresh ? [] : [...pages];
const page = Math.floor(offsetToUse / defaultLimit);
if (res.error) return;
newPages[page] = (res.data as any).results;
setPages(newPages);
setHasMore(!!(res.data as any)?.next);
setTotalCount((res.data as any)?.count);
setOffset(offsetToUse + defaultLimit);
setFetchingNextPage(false);
};
const scrollTop = props.boardRef.current.scrollTop;
const visibleHeight = props.boardRef.current.offsetHeight;
const sectionHeight = props.boardRef.current.scrollHeight;

const items = pages.flat();
if (scrollTop + visibleHeight >= sectionHeight - 100) {
fetchNextPage();
}
}, 200);

useEffect(() => {
const onBoardReachEnd = async () => {
const sectionElementHeight =
sectionRef.current?.getBoundingClientRect().height;
const scrolled = props.boardRef.current?.scrollTop;
// if user has scrolled 3/4th of the current items
if (
scrolled &&
sectionElementHeight &&
scrolled > sectionElementHeight * (3 / 4)
) {
fetchNextPage();
}
};

props.boardRef.current?.addEventListener("scroll", onBoardReachEnd);
props.boardRef.current?.addEventListener("scroll", debouncedScroll);
return () =>
props.boardRef.current?.removeEventListener("scroll", onBoardReachEnd);
}, [props.boardRef, fetchingNextPage, hasMore]);

useEffect(() => {
fetchNextPage(true);
}, [props.section]);
props.boardRef.current?.removeEventListener("scroll", debouncedScroll);
}, [loading, hasMore, debouncedScroll, props.boardRef]); // Add props.boardRef here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid Adding Multiple Event Listeners to the Same Element

Each KanbanSection adds an event listener to the same boardRef element. This results in multiple identical listeners, which is inefficient and can cause unexpected behavior.

Move the scroll event listener to the parent KanbanBoard component to ensure only one listener is attached:

  • In KanbanBoard.tsx, add the scroll listener:
+ const debouncedScroll = useDebounce(
+   () => {
+     // Logic to fetch next pages for all sections or manage state centrally
+   },
+   200,
+   [loading, hasMore]
+ );

useEffect(() => {
  board.current?.addEventListener("scroll", debouncedScroll);
  return () =>
    board.current?.removeEventListener("scroll", debouncedScroll);
}, [debouncedScroll]);
  • Remove the scroll logic from KanbanSection:
function KanbanSection<T extends { id: string }>(
  props: Omit<KanbanBoardProps<T>, "sections" | "onDragEnd"> & {
    section: KanbanBoardProps<T>["sections"][number];
-   boardRef: RefObject<HTMLDivElement>;
  },
) {
  // Remove debouncedScroll and related useEffect
}

This centralizes the scroll handling and prevents multiple listeners on the same element.

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
src/Utils/request/useInfiniteQuery.ts (2)

8-11: Consider making deduplicateBy optional.

The deduplicateBy function is currently mandatory in InfiniteQueryOptions. However, not all infinite queries require deduplication. Making it optional would provide more flexibility while maintaining type safety.

 export interface InfiniteQueryOptions<TItem>
   extends QueryOptions<PaginatedResponse<TItem>> {
-  deduplicateBy: (item: TItem) => string | number;
+  deduplicateBy?: (item: TItem) => string | number;
 }

31-40: Consider extracting the deduplication logic.

The deduplication logic could be useful in other contexts. Consider extracting it into a reusable utility function.

// src/Utils/array.ts
export function deduplicate<T>(
  items: T[],
  keyFn: (item: T) => string | number
): T[] {
  return Array.from(
    items
      .reduce((map, item) => map.set(keyFn(item), item), new Map<string | number, T>())
      .values()
  );
}

Then simplify the code to:

-      const deduplicatedItems = options?.deduplicateBy
-        ? Array.from(
-            allItems
-              .reduce((map, item) => {
-                const key = options.deduplicateBy(item);
-                return map.set(key, item);
-              }, new Map<string | number, TItem>())
-              .values(),
-          )
-        : allItems;
+      const deduplicatedItems = options?.deduplicateBy
+        ? deduplicate(allItems, options.deduplicateBy)
+        : allItems;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d3b8102 and f9249dc.

📒 Files selected for processing (1)
  • src/Utils/request/useInfiniteQuery.ts (1 hunks)
🔇 Additional comments (3)
src/Utils/request/useInfiniteQuery.ts (3)

17-19: Well-implemented state management!

Good choice initializing totalCount with Infinity. This ensures proper handling of the hasMore condition when the total count is not yet known.


27-45: ⚠️ Potential issue

Chain the onResponse callback with options.

The current implementation overwrites any onResponse callback provided in options. Consider chaining the callbacks to preserve caller-provided functionality.

-    onResponse: ({ data }) => {
+    onResponse: (response) => {
+      options?.onResponse?.(response);
+      const { data } = response;
       if (!data) return;
       const allItems = items.concat(data.results);

13-16: Verify integration with useDebounce hook.

Based on the PR comments, this hook should be used with useDebounce to prevent excessive API calls during scrolling.

✅ Verification successful

Integration with useDebounce is properly implemented

The verification shows that useDebounce is correctly integrated with useInfiniteQuery in the Kanban Board component:

  • useDebounce is imported and used in src/components/Kanban/Board.tsx
  • The scroll handler is debounced with a 200ms delay
  • The implementation includes proper loading checks and pagination handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if useDebounce is properly integrated with useInfiniteQuery in the Resource page components

# Look for useInfiniteQuery usage with useDebounce
rg -A 10 "useInfiniteQuery.*useDebounce" 

# Check for scroll event handlers using these hooks
ast-grep --pattern 'const $_ = useCallback(($_, $_) => {
  $$$
  fetchNextPage();
  $$$
}, $_)'

Length of output: 140


Script:

#!/bin/bash
# Let's try a broader search to find any usage of useInfiniteQuery
rg -A 5 "useInfiniteQuery" 

# Also search for scroll handlers separately
rg -A 5 "onScroll|handleScroll"

# And search for useDebounce usage
rg "useDebounce"

# Look for any fetchNextPage calls
rg -A 3 "fetchNextPage"

Length of output: 4086

src/Utils/request/useInfiniteQuery.ts Show resolved Hide resolved
Copy link
Member

@rithviknishad rithviknishad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#9360 Has added support for using TanStack Query. Can we switch to using it's useInfiniteQuery instead after merging your branch with latest develop branch?

Refer docs.

@NikhilA8606
Copy link
Author

#9360 Has added support for using TanStack Query. Can we switch to using it's useInfiniteQuery instead after merging your branch with latest develop branch?

Refer docs.

Yes

@nihal467
Copy link
Member

@NikhilA8606 any update on this issue

@NikhilA8606
Copy link
Author

useInfiniteQuery implemented

@NikhilA8606
Copy link
Author

Any other changes required?

@github-actions github-actions bot added needs-triage question Further information is requested labels Dec 16, 2024
@rithviknishad
Copy link
Member

rithviknishad commented Dec 16, 2024

#9267 (review)

let's not implement the hook. use the one provided by tanstack query instead.

@NikhilA8606
Copy link
Author

#9267 (review)

let's not implement the hook. use the one provided by tanstack query instead.

Okay

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9249dc and a780d43.

📒 Files selected for processing (2)
  • src/components/Resource/ResourceBoard.tsx (7 hunks)
  • src/components/Shifting/ShiftingBoard.tsx (6 hunks)
🔇 Additional comments (6)
src/components/Resource/ResourceBoard.tsx (3)

36-39: LGTM: Type-safe lazy loading implementation.

The implementation correctly uses the generic type parameter to ensure type safety for the lazy-loaded component.


154-156: LGTM: Type-safe item rendering.

The itemRender prop correctly specifies the ResourceModel type for the resource parameter.


Line range hint 138-145: Add error handling and loading state management for infinite scroll.

To address the infinite scroll issue mentioned in the PR objectives, we should add proper error handling and loading state management.

Add error and loading state handling:

 fetchOptions: (id: string) => ({
   route: routes.listResourceRequests,
   options: {
     query: formatFilter({
       ...qParams,
       status: id,
     }),
+    onError: (error) => {
+      console.error('Failed to fetch resources:', error);
+      // Add error notification here
+    },
   },
 }),
src/components/Shifting/ShiftingBoard.tsx (3)

179-188: LGTM: Improved drag-and-drop handling.

The changes add proper null checking for the destination and improve type safety with the DropResult type.


Line range hint 169-177: Add error handling for infinite scroll in ShiftingBoard.

Similar to ResourceBoard, we need proper error handling for the infinite scroll functionality.

Add error handling:

 fetchOptions: (id: string) => ({
   route: routes.listShiftRequests,
   options: {
     query: formatFilter({
       ...qParams,
       status: id,
     }),
+    onError: (error) => {
+      console.error('Failed to fetch shifts:', error);
+      // Add error notification here
+    },
   },
 }),

41-43: Verify the infinite scroll implementation in KanbanBoard.

The PR objectives mention infinite scroll issues, but the implementation details are in the KanbanBoard component.

✅ Verification successful

Based on the Board.tsx implementation found, I can now provide a conclusive response about the infinite scroll implementation:

The infinite scroll implementation in KanbanBoard is properly handled

The implementation includes:

  • Proper infinite scroll using useInfiniteQuery hook with debounced scroll handling
  • Clear loading states and stopping conditions (loading, hasMore flags)
  • Scroll threshold detection for fetching next page
  • Proper cleanup of scroll event listeners
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the infinite scroll implementation in KanbanBoard
ast-grep --pattern 'useInfiniteQuery($$$)' 
rg -A 10 "useInfiniteQuery" --type typescript

Length of output: 125


Script:

#!/bin/bash
# Search for infinite scroll related patterns in the codebase
rg -l "useInfiniteQuery|onScroll|InfiniteScroll|fetchNextPage" --type ts --type tsx

# Check the Board component implementation
fd "Board.tsx" --type f --exec cat {}

# Look for pagination or infinite scroll related props
rg "interface.*Props" -A 10 --type ts --type tsx

Length of output: 19893

Comment on lines +30 to +34
function lazyWithProps<T>(
factory: () => Promise<{ default: React.ComponentType<T> }>,
) {
return lazy(factory) as React.LazyExoticComponent<React.ComponentType<T>>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider moving the lazyWithProps helper to a shared utilities file.

The lazyWithProps helper function is duplicated in both board components. Since it's a generic utility function, it should be moved to a shared location.

Create a new file src/Utils/lazyLoad.ts:

import { lazy } from 'react';

export function lazyWithProps<T>(
  factory: () => Promise<{ default: React.ComponentType<T> }>,
) {
  return lazy(factory) as React.LazyExoticComponent<React.ComponentType<T>>;
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/components/Kanban/Board.tsx (5)

28-29: Consider removing type assertion for query params

The type assertion in the usage of these types (line 96) could be avoided by properly typing the options parameter here.

-      options?: { query?: QueryParams };
+      options?: { query: QueryParams } | { query?: never };

92-97: Remove type assertion in query options

The type assertion on line 96 could be avoided by using proper typing.

-    query: section.fetchOptions(section.id).options?.query as QueryParams,
+    query: section.fetchOptions(section.id).options?.query,

123-131: Optimize IntersectionObserver options

The current threshold of 0.5 might trigger too early or late depending on the item size.

    const observer = new IntersectionObserver(
      (entries) => {
        const lastEntry = entries[0];
        if (lastEntry.isIntersecting) {
          fetchNextPage();
        }
      },
-     { root: currentRef, threshold: 0.5 },
+     { 
+       root: currentRef, 
+       threshold: 0.1,
+       rootMargin: '100px'
+     },
    );

177-181: Enhance loading state feedback

The current loading state could be more informative to users.

-            {loading || isFetching ? (
-              <div className="mt-2 h-[50px] w-[284px] animate-pulse rounded-lg bg-secondary-300">
-                Loading...
-              </div>
-            ) : null}
+            {loading || isFetching ? (
+              <div className="mt-2 h-[50px] w-[284px] animate-pulse rounded-lg bg-secondary-300 p-4">
+                <div className="flex items-center justify-center gap-2">
+                  <div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
+                  <span className="text-sm text-gray-600">Loading more items...</span>
+                </div>
+              </div>
+            ) : null}

141-141: Remove console.log statement

Remove the commented-out console.log statement as it's not needed in production code.

-  // console.log("Items:", items);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a780d43 and b9c0bc3.

📒 Files selected for processing (1)
  • src/components/Kanban/Board.tsx (4 hunks)
🔇 Additional comments (2)
src/components/Kanban/Board.tsx (2)

11-16: LGTM! Clean import organization for pagination types

The new imports are well-organized and properly typed for the pagination functionality.


Line range hint 62-74: LGTM! Clean implementation of horizontal scrolling container

The scroll container implementation with proper overflow handling and ref usage is well done.

Comment on lines +100 to +110
if (data?.results) {
setItems((prevItems) => {
const newItems = data.results.filter(
(newItem) => !prevItems.some((item) => item.id === newItem.id),
);
return [...prevItems, ...newItems];
});
setNextPage(data.next || null);
setTotalCount(data.count || 0);
}
}, [data]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for data synchronization

The effect handles successful data updates but lacks error handling.

  useEffect(() => {
+   if (data?.error) {
+     console.error('Failed to fetch items:', data.error);
+     return;
+   }
    if (data?.results) {
      setItems((prevItems) => {
        const newItems = data.results.filter(
          (newItem) => !prevItems.some((item) => item.id === newItem.id),
        );
        return [...prevItems, ...newItems];
      });
      setNextPage(data.next || null);
      setTotalCount(data.count || 0);
    }
  }, [data]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (data?.results) {
setItems((prevItems) => {
const newItems = data.results.filter(
(newItem) => !prevItems.some((item) => item.id === newItem.id),
);
return [...prevItems, ...newItems];
});
setNextPage(data.next || null);
setTotalCount(data.count || 0);
}
}, [data]);
useEffect(() => {
if (data?.error) {
console.error('Failed to fetch items:', data.error);
return;
}
if (data?.results) {
setItems((prevItems) => {
const newItems = data.results.filter(
(newItem) => !prevItems.some((item) => item.id === newItem.id),
);
return [...prevItems, ...newItems];
});
setNextPage(data.next || null);
setTotalCount(data.count || 0);
}
}, [data]);

@rithviknishad rithviknishad removed question Further information is requested needs-triage labels Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Infinite Scrolling on Resource Page
4 participants