'Apollo Client relayStylePagination doesn't paginate data leading to wrong component state
I've implemented a TableWithData component that creates a Table after fetching the data using a relayStylePagination logic
// index.tsx
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
tableData: relayStylePagination(),
},
},
},
})
const GET_TABLE_DATA = gql`
query GetTableData($first: Int, $after: String) {
tableData(first: $first, after: $after) {
totalCount
edges {
node
cursor
}
pageInfo {
endCursor
hasNextPage
startCursor
hasPreviousPage
}
}
}
`
interface TableWithDataProps {
defaultPageSize?: number
}
export const TableWithData = ({ defaultPageSize = 10 }: TableWithDataProps) => {
const {
data: queryData,
fetchMore,
error,
loading,
} = useQuery<GetTableDataQuery, GetTableDataQueryVariables>(GET_TABLE_DATA, {
variables: {
first: defaultPageSize,
},
notifyOnNetworkStatusChange: true,
})
const edges = queryData?.tableData.edges.map((edge) => edge?.node)
const pageInfo = queryData?.tableData.pageInfo
const totalCount = queryData?.tableData.totalCount
const [data, setData] = React.useState<any[]>([])
const [pageCount, setPageCount] = React.useState(0)
const columns = React.useMemo(
() => [
{
Header: 'Code',
accessor: 'code',
},
{
Header: 'Promo',
accessor: 'promo',
},
{
Header: 'Type',
accessor: 'type',
},
],
[],
)
React.useEffect(() => {
if (loading) {
return
}
if (error) {
console.error(error)
return
}
if (edges) {
setData(edges)
setPageCount(Math.ceil(totalCount ? totalCount / defaultPageSize : 0))
}
}, [loading])
const fetchData = React.useCallback(
({ pageSize }) => {
if (pageInfo?.hasNextPage) {
fetchMore({
variables: {
first: pageSize,
after: pageInfo.endCursor,
},
})
}
},
[pageInfo],
)
return (
<>
<Table
columns={columns}
data={data}
fetchData={fetchData}
loading={loading}
pageCount={pageCount}
/>
</>
)
}
However when the nextPage button in the Table is clicked, the pageIndex variables that holds the page currently displayed is reset to 0 and the fetchMore is called twice. I see from the debug console that apart from the first slice, the following two pages are fetched which data is added to the displayed one leading to a wrong state of the Table.
Here is the Table implementation
interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
columns?: any
data?: any
fetchData?: ({ pageSize }: { pageSize: number }) => void
loading?: boolean
pageCount?: any
}
const Table = ({
columns,
data,
fetchData,
loading,
pageCount: controlledPageCount,
}: TableProps) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
data,
columns,
initialState: { pageIndex: 0, pageSize: 10 },
manualPagination: true,
pageCount: controlledPageCount,
},
useSortBy,
usePagination,
)
React.useEffect(() => {
if (fetchData) fetchData({ pageSize })
}, [pageIndex, pageSize])
return (
<>
<pre>
<code>
{JSON.stringify(
{
pageIndex,
pageSize,
pageCount,
canNextPage,
canPreviousPage,
},
null,
2,
)}
</code>
</pre>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => {
const { key, ...restHeaderGroupProps } =
headerGroup.getHeaderGroupProps()
return (
<tr key={key} {...restHeaderGroupProps}>
{headerGroup.headers.map((column) => {
const { key, ...restColumn } = column.getHeaderProps(
column.getSortByToggleProps(),
)
return (
<th key={key} {...restColumn}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
)
})}
</tr>
)
})}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row: Row<object>, i: number) => {
prepareRow(row)
const { key, ...restRowProps } = row.getRowProps()
return (
<tr key={key} {...restRowProps}>
{row.cells.map((cell) => {
const { key, ...restCellProps } = cell.getCellProps()
return (
<td key={key} {...restCellProps}>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
<tr>
{loading ? (
// Use our custom loading state to show a loading indicator
<td>Loading...</td>
) : (
<td>
Showing {page.length} of ~{controlledPageCount * pageSize}{' '}
results
</td>
)}
</tr>
</tbody>
</table>
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
<select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
)
}
Why is the pageIndex being reset to 0? Why the fetchMore is called twice? And why are the fetched data from the query being added to the previous one?
Here is a clip of the issue
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|

