A higher order reducer (or transducer) that adds pagination, sorting, and filtering to a reducer of an array of objects.
npm install paginated-reduxA higher order Redux reducer (or transducer)
that adds pagination, sorting, and filtering to a reducer of an array of
objects.
It is distributed as an ES5 CommonJS module.
npm install paginated-redux --save-dev
import paginated from 'paginated-redux'
or
const paginated = require('paginated-redux')
This is a "higher order" reducer. That is, it modifies an existing reducer and
extends it with new functionality. It assumes the base reducer's state is simply
an array of objects. It then acts on that array providing the ability to
paginate it, sort it (on a user-definable property of each object in the array),
and filter the array with a string, matching it against the content of the
properties of the objects in the array.
To set it up, let's first assume you have a simple reducer that is an array of
user objects. I'm also assuming, here, that you have some action-constants
defined in a separate file called 'actionTypes.js'.
``javascript
import paginated from 'paginated-redux';
import {
STORE_USERS,
ADD_USER,
UPDATE_USER,
REMOVE_USER,
GOTO_USERS_PAGE,
NEXT_USERS_PAGE,
PREV_USERS_PAGE,
SORT_USERS,
FILTER_USERS
} from 'actionTypes';
const updatedUser = user => {
// ...update user object
return user;
};
// Define base users reducer along with basic user actions.
const users = (state = [], action) => {
switch (action.type) {
case STORE_USERS:
return action.users;
case ADD_USER:
return [...state, action.user];
case UPDATE_USER:
return state.map(updatedUser(action.user));
case REMOVE_USER:
return state.filter(user => (user.id !== action.id));
default:
return state;
}
};
// Extend the users reducer with paginated actions, and pass the user-specific
// pagination actions to the paginated transducer (this way, if you have
// multiple paginated-extended reducers, you can keep each paginated array's
// action separated).
const paginatedUsers = paginated(users, {
GOTO_PAGE: GOTO_USER_PAGE,
NEXT_PAGE: NEXT_USER_PAGE,
PREV_PAGE: PREV_USER_PAGE,
FILTER: FILTER_USERS,
SORT: SORT_USERS
});
// Finally, export the newly extended reducer instead of the base reducer.
export default paginatedUsers;
`
The new extended reducer will now have a modified state. The original array of
user objects will now be stored in a property called list, and some new
pagination-oriented properties will also be added which you can use for
rendering your UI (e.g., what page you're on, total pages, current filter,
etc.).
#### list
This is the total, original, array from the base reducer. When you
modify the base array (e.g., by adding a new object, removing one, etc.) this
array will be updated.
#### pageList
This is an array of objects which represents the current page.
#### cacheListlist
This is mostly used internally, but represents all items from, but filtered against filter which is what's actually used to calculatepageList
the as the pages correspond to what's left over in the cacheList.
#### page
The current page number (starting from 1).
#### totalcacheList
The total pages currently available based on .
#### per
The number of elements that are used for each page.
#### order
The current sort order (either "asc" or "desc").
#### byorder
The current sorting property. For example you could sort an array of user
objects by a property "name" (based on the ) or change this to instead
sort by their "lastModified" property (if those are properties you have in your
user object).
There are new actions you can execute, and define with reducer-specific names,
which will act on your array of objects in certain ways, as well as maintain the
current pagination state.
Note that in the example action creators, I'm using user-specific action names
since these are what I passed into the paginated transducer to act on this user
reducer (e.g., GOTO_PAGE: GOTO_USER_PAGE).
#### GOTO_PAGE
Go to a specific page defined in the action. For example, to go
to page 5, you might write an action creator that looks like this:
`javascript`
export const gotoUserPage = page => ({
type: GOTO_USER_PAGE,
page
});
#### NEXT_PAGEpage
Go to the next page in list. If at the last page, go to the first
page (wraps back around). If you don't want to have this wrapping effect, simply
check, in your UI, if the current in the state is equal to the total
and either disable or remove the "next" button in your UI.
`javascript`
export const nextUserPage = () => ({
type: NEXT_USER_PAGE
});
#### PREV_PAGEpage
Go to the previous page in the list. If on the first page, go to
the last page (wraps back around). If you don't want to have this wrapping
effect, simply check, in your UI, if the current in the state is equal to
1 and either disable or remove the "previous" button in your UI.
`javascript`
export const prevUserPage = () => ({
type: PREV_USER_PAGE
});
#### SORTby
Sort the current paginated list by the property defined in . If theby
current value of in the state is equal to the value passed into the SORTby
action, simply toggle the order (reverse the order that it is currently in). If
the value of in the state is different than the value passed into theSORT action, then re-order the list based on the new value in 'asc' order.
`javascript`
export const sortUsers = by => ({
type: SORT_USERS,
by
});
#### FILTERfilter
Filter the list down by matching any property of the objects in the
array against the value of in the state. This changes the cacheListlist
by creating a new array with all matching objects from that correspond tofilter.
`javascript`
export const filterUsers = filter => ({
type: FILTER_USERS,
filter
});
You can specify some default values for the paginated reducer during creation by
passing a third argument to paginated with a set of options defined as
follows.
`javascript`
const paginatedUsers = paginated(users, {
GOTO_PAGE: GOTO_USER_PAGE,
NEXT_PAGE: NEXT_USER_PAGE,
PREV_PAGE: PREV_USER_PAGE,
SORT: SORT_USERS,
FILTER: FILTER_USERS
}, {
defaultPage = 1,
defaultSortOrder = 'asc',
defaultSortBy = 'name',
defaultPer = 10,
defaultFilter = '',
defaultTotal = 0
})
So, for example, if you want each of your pages to contain 20 objects instead of
10, simply define defaultPer with a value of 20. Note that these are simply
the initial values for these properties. You can change them later in your
program by setting them directly. When you call the paginated actions, the
values of these properties do change ;)
This is a higher order reducer that works with a
Redux reducer, so it doesn't really make
sense outside that context.
It also depends on the base reducer being simply an array of objects (not a
complex object itself).
- add ability to define sort order when calling the action
- add ability to filter based on custom criteria (rather than every property)
- possibly rename cacheList to filteredList as it is more meaningfulpageList
- maybe also rename to pagedList to indicate that it is derivedlist`
from the original
- add ability to use a "flat" array of values rather than objects and still use
all the same functionality
MIT