Skip to content

refactor: extract pagination utility and add hook documentation#123

Merged
0xdevcollins merged 1 commit intoboundlessfi:mainfrom
od-hunter:feat/migrate-to-gragpql
Feb 26, 2026
Merged

refactor: extract pagination utility and add hook documentation#123
0xdevcollins merged 1 commit intoboundlessfi:mainfrom
od-hunter:feat/migrate-to-gragpql

Conversation

@od-hunter
Copy link
Contributor

@od-hunter od-hunter commented Feb 26, 2026

Closes #101

the usebounties already use graphql

Refactor GraphQL bounty hooks with pagination utility and comprehensive documentation

Summary

This PR improves code maintainability and developer experience for the GraphQL-based bounty list hooks by extracting shared pagination logic into a reusable utility and adding comprehensive JSDoc documentation to all related hooks.

Note: The GraphQL migration for bounty list hooks was already completed in a previous implementation. This PR focuses on optimization and documentation.


Changes Made

1. New Pagination Utility (lib/utils/pagination.ts)

  • Created calculateTotalPages() helper function for consistent page count calculation
  • Created formatPaginatedBounties() function to transform GraphQL PaginatedBounties responses to the internal PaginatedResponse<BountyFieldsFragment> format
  • Eliminates code duplication between useBounties() and useInfiniteBounties() hooks
  • Includes comprehensive JSDoc with examples

2. Enhanced Hook Documentation

hooks/use-bounties.ts

  • Added detailed JSDoc explaining the hook's purpose and behavior
  • Documents parameter types and return values
  • Includes practical usage example
  • Refactored to use the new formatPaginatedBounties() utility

hooks/use-infinite-bounties.ts

  • Added comprehensive JSDoc covering infinite scroll pagination strategy
  • Documents automatic page management and result accumulation
  • Includes example showing flatMap pattern for accessing all bounties
  • Refactored to use the new formatPaginatedBounties() utility

hooks/use-bounty-search.ts

  • Added detailed JSDoc explaining search flow with debouncing
  • Documents all returned properties and management functions
  • Includes practical usage example with component integration
  • Added inline comments for constants

Benefits

Reduced Code Duplication - Pagination transformation logic now centralized
Improved Maintainability - Changes to response mapping only need to be made in one place
Better Documentation - JSDoc with examples helps developers use hooks correctly
Type Safety - Utility function ensures consistent pagination object shape
Easier Testing - Pagination logic can now be unit tested independently


GraphQL API Integration

All bounty list operations use the GraphQL API:

  • useBounties() - Uses generated useBountiesQuery hook
  • useInfiniteBounties() - Uses BountiesDocument with GraphQL fetcher
  • useBountySearch() - Uses BountiesDocument with search variables
  • ✅ No REST API calls remain for bounty listing

The GraphQL PaginatedBounties response shape is properly mapped to internal formats:

// GraphQL Response
{
  bounties: Bounty[]
  total: number
  limit: number
  offset: number
}

// Transformed to PaginatedResponse
{
  data: Bounty[]
  pagination: {
    page: number
    limit: number
    total: number
    totalPages: number
  }
}

Testing

  • All modified hooks compile without errors
  • Existing component usage remains unchanged
  • search-command.tsx continues to work with enhanced useBountySearch()
  • Response transformation maintains backward compatibility

Breaking Changes

None - All changes are backward compatible. The hooks' public interfaces remain identical.


Files Modified

File Changes
lib/utils/pagination.ts ✨ New - Pagination utility functions
hooks/use-bounties.ts 📝 Documentation + refactored to use utility
hooks/use-infinite-bounties.ts 📝 Documentation + refactored to use utility
hooks/use-bounty-search.ts 📝 Documentation + inline comments

How to Use This PR

  1. Review the pagination utility in lib/utils/pagination.ts
  2. Check the updated hooks for documentation improvements
  3. Verify the examples work with your components
  4. All existing code continues to work unchanged

Summary by CodeRabbit

  • New Features

    • Bounty search now persists and displays your recent searches for quick access using browser storage.
  • Refactor

    • Improved internal pagination handling across bounty search and browse functionality for better data consistency.

@vercel
Copy link

vercel bot commented Feb 26, 2026

@od-hunter is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR refactors pagination handling in bounty hooks by extracting formatting logic into a dedicated utility module. It adds localStorage-backed recent search persistence to the search hook and creates new pagination utility functions (calculateTotalPages and formatPaginatedBounties) to standardize response transformation across hooks.

Changes

Cohort / File(s) Summary
Pagination Utility Module
lib/utils/pagination.ts
New utility module providing calculateTotalPages() and formatPaginatedBounties() functions to standardize the transformation of GraphQL paginated responses into the internal PaginatedResponse shape with computed totalPages metadata.
Paginated Bounty Hooks
hooks/use-bounties.ts, hooks/use-infinite-bounties.ts
Replaced inline pagination mapping logic with calls to formatPaginatedBounties(). Introduced DEFAULT_LIMIT constant in infinite bounties hook. Added JSDoc documentation.
Search Hook Enhancements
hooks/use-bounty-search.ts
Added localStorage-backed persistence for recent searches via RECENT_SEARCHES_KEY and MAX_RECENT_SEARCHES constants with comprehensive JSDoc documentation of hook behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • 0xdevcollins

Poem

🐰 With pagination neat and clean,
The prettiest utils we've ever seen,
Bounties flow through GraphQL streams,
Recent searches live in dreams,
Formatted responses, oh so divine!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: extracting pagination logic into a utility and adding documentation to hooks.
Linked Issues check ✅ Passed The PR successfully addresses issue #101 requirements: hooks migrated to GraphQL, PaginatedBounties mapped to PaginatedResponse shape, all three hooks updated with documentation, and REST calls removed.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue #101 objectives: pagination utility creation, hook migration to GraphQL, documentation, and removal of REST dependencies are all in-scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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.

🧹 Nitpick comments (3)
hooks/use-bounty-search.ts (2)

105-115: Consider trimming the search term before storing.

The function checks !term.trim() but stores the original term which may have leading/trailing whitespace, potentially causing duplicate entries like "foo" and " foo".

Proposed fix
 const addRecentSearch = (term: string) => {
-  if (!term.trim()) return;
+  const trimmed = term.trim();
+  if (!trimmed) return;

-  const newRecent = [term, ...recentSearches.filter((t) => t !== term)].slice(
+  const newRecent = [trimmed, ...recentSearches.filter((t) => t !== trimmed)].slice(
     0,
     MAX_RECENT_SEARCHES,
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-bounty-search.ts` around lines 105 - 115, The addRecentSearch
function currently checks term.trim() but stores the original term, allowing
entries with surrounding whitespace; modify addRecentSearch to compute const
trimmed = term.trim(), return early if !trimmed, then build newRecent from
trimmed (deduping against recentSearches), call setRecentSearches(newRecent) and
persist JSON.stringify(newRecent) to RECENT_SEARCHES_KEY; reference
addRecentSearch, recentSearches, MAX_RECENT_SEARCHES, setRecentSearches, and
RECENT_SEARCHES_KEY when making the change.

74-85: Consider SSR safety and corrupted data cleanup for localStorage access.

If this code runs during server-side rendering, localStorage will be undefined and cause an error. Additionally, when JSON parsing fails, the corrupted data remains in localStorage, potentially causing repeated errors on subsequent mounts.

Proposed fix
   // Load recent searches on mount
   useEffect(() => {
+    if (typeof window === "undefined") return;
     const saved = localStorage.getItem(RECENT_SEARCHES_KEY);
     if (saved) {
       try {
         // eslint-disable-next-line react-hooks/set-state-in-effect
-        setRecentSearches(JSON.parse(saved));
+        const parsed = JSON.parse(saved);
+        if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
+          setRecentSearches(parsed);
+        } else {
+          localStorage.removeItem(RECENT_SEARCHES_KEY);
+        }
       } catch (e) {
         console.error("Failed to parse recent searches", e);
+        localStorage.removeItem(RECENT_SEARCHES_KEY);
       }
     }
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-bounty-search.ts` around lines 74 - 85, The effect that loads
recent searches should guard against SSR and clean corrupted data: inside the
useEffect that reads RECENT_SEARCHES_KEY, first check that window and
window.localStorage exist (or typeof window !== "undefined") before calling
localStorage.getItem; when JSON.parse fails in the catch block, remove the bad
entry from localStorage (localStorage.removeItem(RECENT_SEARCHES_KEY)) and
optionally reset state via setRecentSearches([]) to avoid repeated failures;
keep the existing setRecentSearches call when parsing succeeds and preserve the
try/catch around JSON.parse in the useEffect.
lib/utils/pagination.ts (1)

38-53: Consider generalizing the formatter for reuse with other entity types.

The function is currently specific to BountyFieldsFragment. If other paginated entities need similar transformation, a generic version would reduce duplication.

Generic alternative
export function formatPaginatedResponse<T>(
  items: T[],
  total: number,
  limit: number,
  page: number,
): PaginatedResponse<T> {
  return {
    data: items,
    pagination: {
      page,
      limit,
      total,
      totalPages: calculateTotalPages(total, limit),
    },
  };
}

// Keep type-safe alias for bounties if desired
export const formatPaginatedBounties = formatPaginatedResponse<BountyFieldsFragment>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/pagination.ts` around lines 38 - 53, Replace the concrete formatter
formatPaginatedBounties with a generic formatPaginatedResponse<T> that accepts
items: T[], total, limit, page and returns PaginatedResponse<T> (reusing
calculateTotalPages for totalPages); then export a type-safe alias const
formatPaginatedBounties = formatPaginatedResponse<BountyFieldsFragment> so
existing callers keep working while other entities can reuse the generic
formatter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@hooks/use-bounty-search.ts`:
- Around line 105-115: The addRecentSearch function currently checks term.trim()
but stores the original term, allowing entries with surrounding whitespace;
modify addRecentSearch to compute const trimmed = term.trim(), return early if
!trimmed, then build newRecent from trimmed (deduping against recentSearches),
call setRecentSearches(newRecent) and persist JSON.stringify(newRecent) to
RECENT_SEARCHES_KEY; reference addRecentSearch, recentSearches,
MAX_RECENT_SEARCHES, setRecentSearches, and RECENT_SEARCHES_KEY when making the
change.
- Around line 74-85: The effect that loads recent searches should guard against
SSR and clean corrupted data: inside the useEffect that reads
RECENT_SEARCHES_KEY, first check that window and window.localStorage exist (or
typeof window !== "undefined") before calling localStorage.getItem; when
JSON.parse fails in the catch block, remove the bad entry from localStorage
(localStorage.removeItem(RECENT_SEARCHES_KEY)) and optionally reset state via
setRecentSearches([]) to avoid repeated failures; keep the existing
setRecentSearches call when parsing succeeds and preserve the try/catch around
JSON.parse in the useEffect.

In `@lib/utils/pagination.ts`:
- Around line 38-53: Replace the concrete formatter formatPaginatedBounties with
a generic formatPaginatedResponse<T> that accepts items: T[], total, limit, page
and returns PaginatedResponse<T> (reusing calculateTotalPages for totalPages);
then export a type-safe alias const formatPaginatedBounties =
formatPaginatedResponse<BountyFieldsFragment> so existing callers keep working
while other entities can reuse the generic formatter.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1c735c5 and 211f07c.

📒 Files selected for processing (4)
  • hooks/use-bounties.ts
  • hooks/use-bounty-search.ts
  • hooks/use-infinite-bounties.ts
  • lib/utils/pagination.ts

@od-hunter
Copy link
Contributor Author

Hi @Benjtalkshow & @0xdevcollins please review

@0xdevcollins 0xdevcollins merged commit 9af9c1c into boundlessfi:main Feb 26, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate useBounties and useInfiniteBounties hooks to GraphQL

2 participants