Skip to content

Refactor Pagination component to improve clarity and maintainability#106

Merged
RoyEJohnson merged 6 commits intomainfrom
core-1448-refactor-pagination
Feb 23, 2026
Merged

Refactor Pagination component to improve clarity and maintainability#106
RoyEJohnson merged 6 commits intomainfrom
core-1448-refactor-pagination

Conversation

@OpenStaxClaude
Copy link
Copy Markdown
Contributor

@OpenStaxClaude OpenStaxClaude commented Dec 22, 2025

Summary

This PR refactors the Pagination component to improve code clarity, testability, and maintainability by applying SOLID principles, specifically the Single Responsibility Principle.

Problem

The original Pagination.tsx component had several issues:

  • Single Responsibility violation: The component handled both complex pagination logic calculation AND rendering
  • Complex algorithm: The range calculation logic (lines 64-107) with nested conditionals was hard to understand and test
  • Poor testability: The pagination algorithm was buried inside the component, making it difficult to unit test independently
  • Lack of documentation: The complex logic lacked explanatory comments

Solution

1. Created Pagination.utils.ts - Pure Pagination Logic

Extracted all pagination calculation logic into pure, well-documented functions:

// Main function that orchestrates the pagination logic
export function calculatePaginationRanges(config: PaginationConfig): PaginationRanges

// Helper functions with clear responsibilities:
- calculateInitialRanges(): Calculates start, middle, and end ranges
- countTotalEntries(): Counts how many page entries will be displayed  
- adjustRangesToMeetMinimum(): Expands ranges to maintain consistent UI size
- range(): Utility to create number arrays

Each function is:

  • Pure: No side effects, same input always produces same output
  • Testable: Can be unit tested in isolation
  • Documented: Extensive JSDoc comments explain the algorithm step-by-step

2. Created Pagination.hooks.ts - React Integration

export function usePaginationRanges(config: PaginationConfig): PaginationRanges
  • Wraps the pure calculation function in a React hook
  • Memoizes results to prevent unnecessary recalculations
  • Only recomputes when relevant props change

3. Refactored Pagination.tsx - Rendering Concerns

Simplified the main component to focus solely on rendering:

  • Extracted sub-components:

    • Ellipsis: Renders the "..." element
    • PageRangeComponent: Renders a range of page links
    • PaginationInfo: Renders the "1-20 of 150" information
  • Simplified main component: Now delegates logic to the hook and focuses on layout

  • Added comprehensive comments: Explains component behavior and usage

  • Maintained API: No breaking changes - component props remain identical

Benefits

Improved Testability: Pure functions can be easily unit tested
Better Readability: Complex algorithm is now documented and broken into logical steps
Easier Maintenance: Each file has a single, clear responsibility
Reusability: Pagination logic can now be used in other contexts if needed
Performance: Memoization prevents unnecessary recalculations
No Breaking Changes: Component API remains identical

Code Comparison

Before (mixed concerns):

// 193 lines with logic and rendering mixed together
export const Pagination = styled((props) => {
  // ... 44 lines of complex range calculation logic ...
  const middleRange = [/*...*/];
  if (numberOfEntries < minEntries) {
    // ... 23 lines of adjustment logic ...
  }
  return (/* rendering */)
});

After (separated concerns):

// Pagination.utils.ts: Pure logic (252 lines with extensive documentation)
export function calculatePaginationRanges(config) { /* ... */ }

// Pagination.hooks.ts: React integration (36 lines)
export function usePaginationRanges(config) {
  return useMemo(() => calculatePaginationRanges(config), [/*...*/]);
}

// Pagination.tsx: Clean rendering (227 lines with comments)
export const Pagination = styled((props) => {
  const ranges = usePaginationRanges({/*...*/});
  return (/* clear, documented rendering */);
});

Testing

The refactoring maintains exact functionality. The component behaves identically to before:

  • Same props API
  • Same rendering output
  • Same visual appearance
  • Existing tests should pass without modification

The new pure functions in Pagination.utils.ts are now easily unit testable if needed.

Related

CORE-1449

Part of an experiment to improve code clarity in the ui-components repository by applying SOLID principles to complex components.


🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This file was broken into three modules which are in the (new) Pagination directory. This file became index.tsx, with pieces extracted to be utils.ts and hooks.ts

Comment thread src/components/Pagination/Pagination.spec.tsx
OpenStaxClaude and others added 4 commits February 19, 2026 09:50
This refactoring addresses Single Responsibility Principle violations by
separating the complex pagination logic from the rendering concerns.

Changes Made:

1. Created Pagination.utils.ts with pure functions:
   - calculatePaginationRanges(): Main algorithm for determining which
     pages to display
   - range(): Utility to create number arrays
   - Helper functions with extensive documentation explaining the
     pagination logic step-by-step

2. Created Pagination.hooks.ts:
   - usePaginationRanges(): Custom hook that memoizes pagination
     calculations
   - Prevents unnecessary re-computations when props haven't changed

3. Refactored Pagination.tsx:
   - Extracted sub-components (Ellipsis, PageRangeComponent,
     PaginationInfo)
   - Simplified main component to focus on rendering
   - Added comprehensive JSDoc comments explaining component behavior
   - Maintained exact same functionality and API

Benefits of this refactoring:

- Testability: Pure functions in utils file can be easily unit tested
- Readability: Complex algorithm is now documented and broken into
  logical steps
- Maintainability: Each file has a single, clear responsibility
- Reusability: Pagination logic can now be used in other contexts
- Performance: Memoization prevents unnecessary recalculations

No breaking changes - the component API remains identical.

Fixes CORE-1448

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Full code coverage for Pagination
Removed comments that were:
- Stating obvious things (e.g., 'Renders an ellipsis' for Ellipsis component)
- Redundant with code (e.g., 'Only recalculate if values change' for useMemo)
- JSX comments that just describe the structure without adding value

Kept comments that:
- Explain complex logic (e.g., ternary conditions in adjustRangesToMeetMinimum)
- Provide useful context (e.g., JSDoc for public APIs)
- Include examples or non-obvious behaviors
And adjust chatController for 100% coverage
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the Pagination component to improve maintainability and testability by applying the Single Responsibility Principle. The complex pagination logic has been extracted into pure, well-documented utility functions, while the React component focuses solely on rendering.

Changes:

  • Extracted pagination calculation logic into utils.ts with comprehensive JSDoc documentation
  • Created hooks.ts to provide React integration with memoization
  • Refactored index.tsx to focus on rendering with clear sub-components
  • Updated tests and snapshots to reflect the new file structure
  • Upgraded ESLint and related dev dependencies from 8.19.0 to 8.57.1

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/components/Pagination/utils.ts New file containing pure pagination calculation functions with extensive documentation
src/components/Pagination/hooks.ts New file with React hook that memoizes pagination calculations
src/components/Pagination/index.tsx Refactored component focusing on rendering, using extracted logic
src/components/Pagination.tsx Deleted - functionality moved to directory structure
src/components/Pagination/Pagination.spec.tsx Updated imports and added new test for utils
src/components/Pagination/Pagination.stories.tsx Updated import path
src/components/Pagination/__snapshots__/Pagination.spec.tsx.snap Updated snapshots reflecting test changes
src/components/HelpMenu/chatController.ts Unrelated fix: prevents stale closure bug with popup reference
package.json Unrelated: alphabetical reordering of @types/dompurify
yarn.lock Dev dependency updates: ESLint 8.19.0 → 8.57.1 and related packages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* @param config - Pagination configuration
* @returns Calculated pagination ranges with ellipsis indicators
*/
export function calculatePaginationRanges(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see a specific spec for this, it might be nice to have.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's the central call for usePaginationRanges which is exercised by tests of the Pagination widget.

@RoyEJohnson RoyEJohnson merged commit ee18684 into main Feb 23, 2026
3 checks passed
@RoyEJohnson RoyEJohnson deleted the core-1448-refactor-pagination branch February 23, 2026 19:50
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.

4 participants