Skip to content

Instantly share code, notes, and snippets.

@justinobney
Last active June 7, 2018 20:47
Show Gist options
  • Save justinobney/66015f7e53805cbc846c0e6b98d80341 to your computer and use it in GitHub Desktop.
Save justinobney/66015f7e53805cbc846c0e6b98d80341 to your computer and use it in GitHub Desktop.
Pagination.md
<PaginationContainer
  // used in conjunction with internal state: totalCount to calculate pages
  pageSize={10}
  // if passed, will not invoke onPageChanged on component mount
  initialValues={{items: [1,2,3, ...], totalCount: 100, currentPage: 3}}
  // function: nextPage => Promise<{items:number, totalCount:number}>
  onPageChange={async (nextPage) => {
    const {result} = await fetch(nextPage);
    return {
      items: result.data,
      totalCount: result.totalCount,
    }
  }}
>
  {/* Should 'items' be the previous page's items when loading the next page or blank? */}
  ({loading, items, PaginationComponent}) => (
    <FormContainer loading={loading}>
      {/* render your items how you want */}
      <pre>{JSON.stringify(items)}</pre>
      {/* place the PaginationComponent you want */}
      {PaginationComponent}
    </FormContainer>
  )
</PaginationContainer>
import React, {Component} from 'react';
import {Menu} from 'semantic-ui-react';
import segmentize from 'segmentize';
import min from 'lodash/min';
import max from 'lodash/max';
import p from 'prop-types';
export class PaginationContainer extends Component {
static propTypes = {
render: p.func,
children: p.func,
onPageChange: p.func.isRequired,
pageSize: p.number,
initialValues: p.shape({
items: p.array.isRequired,
totalCount: p.number.isRequired,
currentPage: p.number.isRequired,
}),
};
static getDerivedStateFromProps(nextProps, prevState) {
const {initialValuesSet} = prevState;
if (nextProps.initialValues && !initialValuesSet) {
return {
...nextProps.initialValues,
initialValuesSet: true,
};
}
return null;
}
state = {
initialValuesSet: false,
loading: false,
error: undefined,
items: [],
totalCount: 0,
currentPage: 1,
};
componentDidMount() {
if (!this.props.initialValues) {
this._handlePageChanged(this.state.currentPage);
}
}
_handlePageChanged = async page => {
try {
this.setState({loading: true, error: undefined});
const {items, totalCount} = await this.props.onPageChange(page);
this.setState({items, totalCount, currentPage: page});
} catch (error) {
this.setState({error});
} finally {
this.setState({loading: false});
}
};
render() {
const {render, children, pageSize = 10} = this.props;
const {loading, error, items, currentPage, totalCount} = this.state;
const renderFn = render || children;
const totalPages = Math.ceil(totalCount / pageSize);
const PaginationComponent = (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={this._handlePageChanged}
/>
);
return renderFn({
items,
currentPage,
totalCount,
loading,
error,
PaginationComponent,
});
}
}
export class Pagination extends Component {
static propTypes = {
currentPage: p.number.isRequired,
totalPages: p.number.isRequired,
onPageChange: p.func.isRequired,
};
render() {
const {currentPage = 1, totalPages = 0, onPageChange} = this.props;
if (totalPages === 0) {
return null;
}
const {
beginPages,
previousPages,
centerPage,
nextPages,
endPages,
} = segmentize({
page: currentPage,
pages: totalPages,
beginPages: 1,
endPages: 1,
sidePages: 2,
});
const leftGap = min(previousPages) - max(beginPages);
const showLeftGap = !isNaN(leftGap) && leftGap > 1;
const rightGap = min(endPages) - max(nextPages);
const showRightGap = !isNaN(rightGap) && rightGap > 1;
return (
<Menu pagination>
{beginPages.map(page => (
<Menu.Item
key={page}
name={`${page}`}
onClick={() => onPageChange(page)}
/>
))}
{showLeftGap && (
<Menu.Item key="left gap" icon="ellipsis horizontal" disabled />
)}
{previousPages.map(page => (
<Menu.Item
key={page}
name={`${page}`}
onClick={() => onPageChange(page)}
/>
))}
{centerPage.map(page => (
<Menu.Item key={page} name={`${page}`} active />
))}
{nextPages.map(page => (
<Menu.Item
key={page}
name={`${page}`}
onClick={() => onPageChange(page)}
/>
))}
{showRightGap && (
<Menu.Item key="right gap" icon="ellipsis horizontal" disabled />
)}
{endPages.map(page => (
<Menu.Item
key={page}
name={`${page}`}
onClick={() => onPageChange(page)}
/>
))}
</Menu>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment