Skip to content

Instantly share code, notes, and snippets.

@sibelius
Created November 28, 2019 14:30
Show Gist options
  • Save sibelius/762fb2d808756e21412e5624ec02f21c to your computer and use it in GitHub Desktop.
Save sibelius/762fb2d808756e21412e5624ec02f21c to your computer and use it in GitHub Desktop.
back and forward pagination
import dot from 'dot-object';
import { stringify } from 'query-string';
import React, { useState } from 'react';
import { RelayRefetchProp } from 'react-relay';
import { RouteComponentProps, useHistory } from 'react-router-dom';
export type PaginationProps = {
handleRowsPerPageChange: (quantity: number) => void;
totalItems: number;
rowsPerPage: number;
firstItemIndex: number;
lastItemIndex: number;
handlePageChange: (isForward: boolean) => void;
};
type PageInfo = {
hasNextPage: boolean;
startCursor: string | null;
endCursor: string | null;
};
type Edge<T> = {
cursor: string;
node: T;
};
type Connection<T> = {
count: number;
totalCount: number;
endCursorOffset: number;
startCursorOffset: number;
pageInfo: PageInfo;
edges: Edge<T>[];
};
type Props<T> = {
children: (edges: Edge<T>[], paginationProps?: PaginationProps) => React.ReactNode;
connection: Connection<T>;
relay: RelayRefetchProp;
getFragmentVariables?: () => object;
getRefetchRenderVariables?: () => object;
} & RouteComponentProps;
export const useRelayPagination = <T extends object>(props: Props<T>) => {
const { connection, relay, getFragmentVariables, getRefetchRenderVariables } = props;
const history = useHistory();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [quantityPerPage, setQuantityPerPage] = useState<number>(
getFiltersFromLocation(['quantity']).quantity || props.quantityPerPage || 10,
);
const safeGetFragmentVariables = () => {
if (getFragmentVariables) {
return getFragmentVariables();
}
return {};
};
const getRenderVariables = () => {
if (getRefetchRenderVariables) {
return getRefetchRenderVariables();
}
return {};
};
const loadPageForwardVars = (quantityPerPageChange: number) => {
const { quantity } = getQuery();
const { endCursorOffset, startCursorOffset } = connection;
const items = connection.edges.slice(startCursorOffset, endCursorOffset);
const lastItem = items[items.length - 1];
const refetchVariables = (fragmentVariables: any) => ({
...fragmentVariables,
...safeGetFragmentVariables(),
first: parseInt(quantity || quantityPerPage, 10) || 10,
after: quantityPerPageChange ? null : lastItem.cursor,
});
return refetchVariables;
};
const loadPageBackwardsVars = () => {
const { quantity } = getQuery();
const { endCursorOffset, startCursorOffset } = connection;
const firstItem = connection.edges.slice(startCursorOffset, endCursorOffset)[0].cursor;
const refetchVariables = (fragmentVariables: any) => ({
...fragmentVariables,
...safeGetFragmentVariables(),
last: parseInt(quantity || quantityPerPage, 10) || 10,
first: null,
before: firstItem,
});
return refetchVariables;
};
const loadPage = (isForward: boolean, quantity?: number) => {
setIsLoading(true);
const refetchVariables = isForward ? loadPageForwardVars(quantity) : loadPageBackwardsVars();
const renderVariables = getRenderVariables();
relay.refetch(refetchVariables, renderVariables, () => {
setIsLoading(false);
});
};
const handleRowsPerPageChange = (quantity: number) => {
const { pathname } = location;
const queryParams = getQuery();
const search = stringify(dot.dot({ ...queryParams, quantity }));
history.replace({ pathname, search });
if (setQuantityPerPage) {
setQuantityPerPage(quantity);
}
loadPage(true, quantity);
};
const handleLoading = (value: boolean, callback: () => void) => {
setIsLoading(value);
callback && callback();
};
const { count, totalCount, endCursorOffset, startCursorOffset } = connection;
const slicedEdges = connection.edges.slice(startCursorOffset, endCursorOffset);
const paginationProps = {
handleRowsPerPageChange,
rowsPerPage: quantityPerPage,
totalItems: count || totalCount,
firstItemIndex: startCursorOffset,
lastItemIndex: endCursorOffset,
handlePageChange: loadPage,
isLoading,
};
return {
edges: slicedEdges,
paginationProps,
handleLoading,
isLoading,
setIsLoading,
quantityPerPage,
setQuantityPerPage,
};
};
export default useRelayPagination;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment