table pagination

What I'm trying to do is quite simple: my table has pagination. So if I have 12 items to show and my max items per page is 10, I will have 2 pages, one with 10 records and another with just 2. I created a code that works just fine, but it seems rather dirty. What is the best way to have the same outcome as the code below, but with fewer lines and cleaner? I'm looking for less nesting, fewer variables, whatever makes it simpler.

Note: props.content contains all items;

let pagesContainer = [];
let page = [];
let i = 0;
for (const content of props.content) {
if (i < itemsPerPage) {
page.push(content);
i++;
} else {
pagesContainer.push(page)
page = [];
page.push(content)
i = 0;
}
if (props.content.indexOf(content) === props.content.length - 1) {
pagesContainer.push(page);
}
}
console.log(pagesContainer);


Similar to this pagination question. What I'm trying to do is to create an array, which each position represents a page and stores its items, not trying to get ranges, pages amounts, etc.

Refactor

First of all, let's extract clear & separate function

function getPaged(items, itemsPerPage) {
let pagesContainer = [];
let page = [];
let i = 0;
for (const content of items) {
if (i < itemsPerPage) {
page.push(content);
i++;
} else {
pagesContainer.push(page)
page = [];
page.push(content)
i = 0;
}
if (items.indexOf(content) === items.length - 1) {
pagesContainer.push(page);
}
}

return pagesContainer;
}

console.log(getPaged(props.content, itemsPerPage));


Now, you can notice that the variable i always equals to page.length. So we don't need it. Just replace usage i with page.length:

function getPaged(items, itemsPerPage) {
let pagesContainer = [];
let page = [];
for (const content of items) {
if (page.length < itemsPerPage) {
page.push(content);
} else {
pagesContainer.push(page)
page = [];
page.push(content);
}
if (items.indexOf(content) === items.length - 1) {
pagesContainer.push(page);
}
}

return pagesContainer;
}

console.log(getPaged(props.content, itemsPerPage));


Condition if (items.indexOf(content) === items.length - 1) is not lovely. We don't really need to add last page inside of cycle. Let's add that page outside:

function getPaged(items, itemsPerPage) {
let pagesContainer = [];
let page = [];
for (const content of items) {
if (page.length < itemsPerPage) {
page.push(content);
} else {
pagesContainer.push(page)
page = [];
page.push(content);
}
}

if (page.length > 0) {
pagesContainer.push(page);
}

return pagesContainer;
}

console.log(getPaged(props.content, itemsPerPage));


There is no reason to create a page & push a new item into it. Because we can create a page with the new item inside.

function getPaged(items, itemsPerPage) {
let pagesContainer = [];
let page = [];
for (const content of items) {
if (page.length < itemsPerPage) {
page.push(content);
} else {
pagesContainer.push(page)
page = [content];
}
}

if (page.length > 0) {
pagesContainer.push(page);
}

return pagesContainer;
}

console.log(getPaged(props.content, itemsPerPage));


Beautify

Let's make some renames in order to make our life shorter:

• itemsPerPage → pageSize
• pagesContainer → pages
• content → item

Also, we should note that the variable pages (previously pagesContainer) is not modified. So we can use const.

function getPaged(items, pageSize) {
const pages = [];
let page = [];
for (const item of items) {
if (page.length < pageSize) {
page.push(item);
} else {
pages.push(page)
page = [item];
}
}

if (page.length > 0) {
pages.push(page);
}

return pages;
}

console.log(getPaged(props.content, itemsPerPage));


Possible enhancement

We can build lazy solution via generators.

function* getPaged(items, pageSize) {
let page = [];
for (const item of items) {
if (page.length < pageSize) {
page.push(item);
} else {
yield page;
page = [item];
}
}

if (page.length > 0) {
yield page;
}
}

console.log(Array.from(getPaged(props.content, itemsPerPage)));


This is cool only in case if you need to post-process your pages in lazy way.

Alternate solution 1

Our alternate solution will be based on a functional top-down approach. Firstly we will generate all pages from 0 to pageCount; And then we will fill each page with its actual content.

const range = (size) => [...Array(size).keys()];

function getPaged(items, pageSize) {
const pageCount = Math.ceil(items.length / pageSize);

const getPage = (pageNo) => range(pageSize)
.map(noInPage => items[pageNo * pageSize + noInPage])
.filter(item => item !== undefined);

return range(pageCount)
.map(pageNo => getPage(pageNo));
}

console.log(getPaged(props.content, itemsPerPage));


Please, note that this solution will be slower. This is because map and filter are slow. But this can be solved with transducers

Alternate solution 2

Use lodash/chunk and don't write your own function

Alternate solution 3

Using lodash/groupBy, group items by page. Then return only values of the resulting dictionary.

const getPaged = (items, pageSize) =>
Object.values(_.groupBy(
items,
(item, index) => Math.floor(index / pageSize)
));

console.log(getPaged(props.content, itemsPerPage));

• Very well described. Loved your answer. Will look into Lodash, it seems like a good framework, otherwise will use the beautified version you provided. Aug 19, 2021 at 1:11