Skip to content

feat: Paginate and memoize the admin QuestTable rendering - #2164

Merged
RUKAYAT-CODER merged 4 commits into
EarnQuestOne:mainfrom
Adeyemi-cmd:Paginate_and_memoize
Aug 20, 2026
Merged

feat: Paginate and memoize the admin QuestTable rendering#2164
RUKAYAT-CODER merged 4 commits into
EarnQuestOne:mainfrom
Adeyemi-cmd:Paginate_and_memoize

Conversation

@Adeyemi-cmd

Copy link
Copy Markdown
Contributor

Close #2151

Title

perf(admin): paginate and memoize quest table rows

Summary

The admin quest table rendered the full quest list at once and rebuilt the row
action handlers for every row on every render, making the admin surface slow and
janky as the quest count grows.

This PR:

  1. Adds client-side pagination to QuestTable (10 / 25 / 50 / 100 rows per
    page, default 10) with Previous / Next controls, a page indicator, and a
    "Showing X–Y of Z" range readout. The page resets to 1 whenever the quests
    prop changes identity (filter, sort, or refetch), so users never land on an
    empty page.
  2. Extracts each row into a React.memo'd QuestTableRow and wraps
    QuestRowActions in React.memo, so a single checkbox toggle or sort
    change no longer re-renders every row.
  3. Stabilizes the row action callbacks (onToggleSelect, onEdit,
    onDelete) with useCallback so the memoized rows are not invalidated by
    incidental re-renders of the table.

QuestTableProps is unchanged — QuestManager and the admin page required no
modifications, and existing functionality is preserved.

Motivation

Rendering thousands of <tr> elements and rebuilding every row's handlers on
each state change produced:

  • Slow initial paint of the admin quest list (mount cost scales linearly with
    quest count).
  • Janky interactions — every checkbox toggle / sort triggered a full-list
    re-render (selection update cost scales with quest count).

Changes

components/admin/QuestTable.tsx

  • Added pagination state (page, pageSize) with PAGE_SIZE_OPTIONS and
    DEFAULT_PAGE_SIZE.
  • Added currentPage clamping and a useEffect that resets to page 1 when the
    quests prop changes.
  • Added visibleQuests useMemo slice.
  • Extracted memoized QuestTableRow (checkbox, title/category, status badge,
    reward, participants, deadline, actions).
  • Wrapped onToggleSelect, onEdit, and onDelete in useCallback.
  • Added a pagination footer (range text, rows-per-page select, Previous / Next,
    "Page X of Y" indicator) with data-testids for testing.
  • Footer is hidden while loading or when the list is empty.

components/admin/QuestRowActions.tsx

  • Wrapped the component in React.memo to prevent needless re-renders.

Benchmark harness (new)

  • scripts/benchmarks/quest-table.render.bench.tsx — measures mount time and
    selection-update time for datasets of 200 and 1000 quests (best-of-3,
    jsdom), writing JSON results.
  • vitest.benchmark.config.ts — dedicated Vitest config so benchmarks never
    run as part of the normal npm test suite.
  • package.json — new benchmark / benchmark:quest-table scripts.
  • Committed results: scripts/benchmarks/results/quest-table.baseline.json
    (before) and quest-table.optimized.json (after).

Tests (new)

  • components/admin/__tests__/QuestTable.pagination.test.tsx — page-size-bound
    rendering, Previous / Next navigation, boundary disabling, page reset on new
    quests, page-size change, and absence of the footer for loading / empty
    states.
  • components/admin/__tests__/QuestTable.memo.test.tsx — verifies that
    changing the selection re-renders only the affected row, and that changing
    the callback identities re-renders all rows (guards against over-memoizing).

Docs

  • docs/ADMIN_QUEST_TABLE_PERFORMANCE.md — full write-up with methodology,
    before/after numbers, and how to re-run the benchmark.
  • CHANGELOG.md — added entry under Unreleased with the before/after table.

Before/After Performance

Benchmark: best-of-3 render timings in jsdom (mount = initial render of N
quests; update = re-render after a single-row selection change).

Dataset Metric Before After Speed-up
200 quests Mount 237.84 ms 22.79 ms ~10x
200 quests Selection update 181.18 ms 7.97 ms ~23x
1000 quests Mount 856.12 ms 15.13 ms ~57x
1000 quests Selection update 535.67 ms 7.31 ms ~73x
1000 quests Rows mounted 1000 10 flat (page-size bound)

Key property: cost is now flat — mounting 1000 quests is no more expensive
than mounting 200, because only one page of rows is rendered and unchanged rows
are never re-rendered.

Re-run with:

cd FrontEnd/my-app
npm run benchmark:quest-table

Testing

  • npm test (frontend): 809 passed / 3 failed — the 3 failures
    (app/layout.test.tsx, lib/hooks/useClaim.test.ts, lib/hooks/useReputation.test.tsx)
    are pre-existing on the base branch and unrelated to this change (verified by
    running them against a clean checkout).
  • New tests: QuestTable.pagination.test.tsx (7) and
    QuestTable.memo.test.tsx (2) all pass; the full admin component suite (60
    tests) passes.
  • npm run lint — clean on all touched files.
  • npm run typecheck — no new errors (the 7 remaining errors are pre-existing
    on the base branch).
  • prettier --check — clean on all touched files.
  • Root npm test (repo tooling suite) — passes.

Acceptance Criteria

  • Measurable performance improvement demonstrated with before/after numbers
  • No regression in existing functionality (all pre-existing tests pass;
    the 3 failures exist on the base branch too)
  • Tests pass and code follows project standards (lint, prettier, typecheck)
  • Change is documented (docs + CHANGELOG + benchmark results committed)

Notes / Follow-ups

  • Pagination is client-side and applied on top of the already-filtered /
    sorted list in useQuestFilters, so it needs no server changes.
  • If the quest list grows very large, the paginated table could later be
    swapped for the existing VirtualizedList windowing component, but
    pagination is simpler for a tabular admin surface and matches the stated
    implementation plan.

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Well done on the task done so far!
Kindly fix workflow to pass

- Format all files with prettier to pass format:check
- Add @contracts and @stellar/stellar-sdk aliases to vitest.config so
  useClaim.test resolves contract bindings
- Import React in useReputation.test to fix 'React is not defined'
- Guard nullable useParams/useSearchParams/usePathname results so
  next build passes with the pages-router compat navigation types
- Ignore generated benchmark output (scripts/benchmarks/results/*.latest.json)
@Adeyemi-cmd

Copy link
Copy Markdown
Contributor Author

@RUKAYAT-CODER...i'm so sorry for the delay...please review

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Thank you for contributing to the project.

@RUKAYAT-CODER
RUKAYAT-CODER merged commit b9cbe10 into EarnQuestOne:main Aug 20, 2026
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.

Paginate and memoize the admin QuestTable rendering

2 participants