javascript 71 lines · 9 steps

A closure-based pagination helper in JS

A factory function keeps the current page private and exposes a small read/navigate API that the DOM renders against.

Explained by highlit
1function usePagination(items, pageSize = 10) {
2 let currentPage = 1;
3 const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
4 
5 function clamp(page) {
6 return Math.min(Math.max(page, 1), totalPages);
7 }
8 
9 function slice() {
10 const start = (currentPage - 1) * pageSize;
11 return items.slice(start, start + pageSize);
12 }
13 
14 return {
15 get page() {
16 return currentPage;
17 },
18 get totalPages() {
19 return totalPages;
20 },
21 get hasPrevious() {
22 return currentPage > 1;
23 },
24 get hasNext() {
25 return currentPage < totalPages;
26 },
27 get items() {
28 return slice();
29 },
30 goTo(page) {
31 currentPage = clamp(page);
32 return slice();
33 },
34 next() {
35 return this.goTo(currentPage + 1);
36 },
37 previous() {
38 return this.goTo(currentPage - 1);
39 },
40 };
41}
42 
43function renderControls(pager, container) {
44 container.innerHTML = `
45 <button data-action="prev" ${pager.hasPrevious ? "" : "disabled"}>Previous</button>
46 <span>Page ${pager.page} of ${pager.totalPages}</span>
47 <button data-action="next" ${pager.hasNext ? "" : "disabled"}>Next</button>
48 `;
49 
50 container.querySelector('[data-action="prev"]').onclick = () => {
51 pager.previous();
52 renderList(pager);
53 renderControls(pager, container);
54 };
55 container.querySelector('[data-action="next"]').onclick = () => {
56 pager.next();
57 renderList(pager);
58 renderControls(pager, container);
59 };
60}
61 
62function renderList(pager) {
63 const list = document.getElementById("list");
64 list.replaceChildren(
65 ...pager.items.map((item) => {
66 const li = document.createElement("li");
67 li.textContent = item.title;
68 return li;
69 })
70 );
71}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A factory function plus closure hides mutable state behind a controlled, read-only surface.
  2. 2Getters let callers observe derived state like hasNext without exposing the underlying variable.
  3. 3Keeping navigation and rendering separate means the UI just reacts to whatever the pager reports.

Related explainers

javascript
const ROLE_PERMISSIONS = {
  admin: ['users:read', 'users:write', 'billing:read', 'billing:write'],
  manager: ['users:read', 'billing:read'],
  member: ['users:read'],

Role-based permissions middleware in Express

authorization middleware rbac
Intermediate 9 steps
javascript
function attachThousandSeparators(input, { locale = 'en-US' } = {}) {
  const formatter = new Intl.NumberFormat(locale);
  const groupSep = formatter.format(11111).replace(/\d/g, '')[0] || ',';
  const decimalSep = formatter.format(1.1).replace(/\d/g, '')[0] || '.';

Live thousand separators without losing the caret

dom intl caret-preservation
Advanced 8 steps
javascript
import { useReducer, useEffect } from "react";
 
const initialState = { status: "idle", data: null, error: null };
 

Building a data-fetching hook in React

custom-hooks usereducer data-fetching
Intermediate 9 steps
javascript
const express = require('express');
const app = express();
 
app.get('/health', (req, res) => res.json({ status: 'ok' }));

Graceful shutdown in an Express server

graceful-shutdown signal-handling connection-tracking
Advanced 9 steps
javascript
import { useState, useEffect, useCallback } from 'react';
 
function getColumnCount(width) {
  if (width < 640) return 1;

A responsive column hook in React

custom-hooks debouncing responsive-design
Intermediate 7 steps
javascript
function initCharacterCounter(textarea, options = {}) {
  const maxLength = options.maxLength ?? 280;
  const warnThreshold = options.warnThreshold ?? 0.9;
 

A live character counter for textareas

dom closures accessibility
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

A closure-based pagination helper in JS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code