当总数未知时如何对 react-admin 列表进行分页?
How to paginate react-admin lists when the total is unknown?
总结:我无法从我的 GraphQL 端点获取记录总数。当我解析来自端点的响应时,我只知道我是否已经到达我的 GraphQL 记录列表的末尾。如何让我的自定义分页组件知道它在最后一页?
详细信息:我使用 ra-data-graphql
将 React Admin 与 AWS AppSync(DynamoDB 上的 GraphQL)结合使用。 AppSync 无法告诉您列表查询可用的记录总数,它还会将您可以 return 的记录数限制为 1MB 负载。相反,如果有更多记录要查询,它会包含一个 nextToken
值,您可以将其包含在后续列表查询中。
我创建了一个仅使用 "prev" 和 "next" 链接的自定义分页组件,这很好。但是我需要知道什么时候显示最后一页。现在,我只在传递给 buildQuery()
的 parseResponse()
函数中知道这一点以进行列表查询。此时,我可以访问 nextToken
值。如果它是空的,那么我已经从 AppSync 获取了最后一页结果。如果我可以传递这个值,甚至是一个布尔值,例如lastPage
到自定义分页组件,我已经准备好了。我如何在 React Admin 中执行此操作?
为了实现这一点,我创建了一个自定义缩减器,nextTokenReducer
用于查找 React Admin 的 CRUD_GET_LIST_SUCCESS
操作,其有效负载是来自 AppSync GraphQL 端点的整个响应。我可以从中提取 nextToken
值:
import { CRUD_GET_LIST_SUCCESS } from "react-admin";
export default (previousState = null, { type, payload }) => {
if (type === CRUD_GET_LIST_SUCCESS) {
return payload.nextToken;
}
return previousState;
};
我将这个自定义减速器传递给了我的主要 App
组件中的 Admin
组件:
import nextTokenReducer from "./reducers/nextTokenReducer";
...
class App extends Component {
...
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return <div>Loading</div>;
}
return (
<Admin
customReducers={{ nextToken: nextTokenReducer }}
dataProvider={dataProvider}
>
<Resource name="packs" list={PackList} />
</Admin>
);
}
}
然后我将 nextToken
商店连接到我的自定义分页组件。它会显示 "next"、"prev" 或什么都不显示,这取决于 nextToken
是否在其属性中:
import React from "react";
import Button from "@material-ui/core/Button";
import ChevronLeft from "@material-ui/icons/ChevronLeft";
import ChevronRight from "@material-ui/icons/ChevronRight";
import Toolbar from "@material-ui/core/Toolbar";
import { connect } from "react-redux";
class CustomPagination extends React.Component {
render() {
if (this.props.page === 1 && !this.props.nextToken) {
return null;
}
return (
<Toolbar>
{this.props.page > 1 && (
<Button
color="primary"
key="prev"
icon={<ChevronLeft />}
onClick={() => this.props.setPage(this.props.page - 1)}
>
Prev
</Button>
)}
{this.props.nextToken && (
<Button
color="primary"
key="next"
icon={<ChevronRight />}
onClick={() => this.props.setPage(this.props.page + 1)}
labelposition="before"
>
Next
</Button>
)}
</Toolbar>
);
}
}
const mapStateToProps = state => ({ nextToken: state.nextToken });
export default connect(mapStateToProps)(CustomPagination);
最后,我将自定义分页组件传递到我的列表组件中:
import React from "react";
import { List, Datagrid, DateField, TextField, EditButton } from "react-admin";
import CustomPagination from "./pagination";
export const PackList = props => (
<List {...props} pagination={<CustomPagination />}>
<Datagrid>
...
</Datagrid>
</List>
);
还有一种方法可以使 AppSync 解析器适应 page
和 perPage
本机 react-admin 参数。
这是一个不好的做法,因为查询响应被限制为 1MB,而且还需要为每个页面查询解析和转换完整的 dynamodb 查询响应,但它确实有效。
VTL AppSync 解析器请求映射模板:
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression": "userId = :userId",
"expressionValues" : {
":userId" : $util.dynamodb.toDynamoDBJson($context.identity.sub)
}
}
}
VTL AppSync 解析器响应映射模板:
#set($result = {})
#set($result.items = [])
#set($result.length = $ctx.result.items.size())
#set($start = $ctx.arguments.perPage * ($ctx.arguments.page - 1))
#set($end = $ctx.arguments.perPage * $ctx.arguments.page - 1)
#if($end > $result.length - 1)
#set($end = $result.length - 1)
#end
#if($start <= $result.length - 1 && $start >= 0 )
#set($range = [$start..$end])
#foreach($i in $range)
$util.qr($result.items.add($ctx.result.items[$i]))
#end
#end
$util.toJson($result)
dataProvider.js
...
const buildQuery = () => (
raFetchType,
resourceName,
params
) => {
if (resourceName === "getUserToDos" && raFetchType === "GET_LIST") {
return {
query: gql`
query getUserToDos($perPage: Int!, $page: Int!) {
getUserToDos(perPage: $perPage, page: $page) {
length
items {
todoId
date
...
}
}
}
`,
variables: {
page: params.pagination.page,
perPage: params.pagination.perPage
},
parseResponse: ({ data }) => {
return {
data: data.getUserToDos.items.map(item => {
return { id: item.listingId, ...item };
}),
total: data.getUserToDos.length
};
}
};
}
...
总结:我无法从我的 GraphQL 端点获取记录总数。当我解析来自端点的响应时,我只知道我是否已经到达我的 GraphQL 记录列表的末尾。如何让我的自定义分页组件知道它在最后一页?
详细信息:我使用 ra-data-graphql
将 React Admin 与 AWS AppSync(DynamoDB 上的 GraphQL)结合使用。 AppSync 无法告诉您列表查询可用的记录总数,它还会将您可以 return 的记录数限制为 1MB 负载。相反,如果有更多记录要查询,它会包含一个 nextToken
值,您可以将其包含在后续列表查询中。
我创建了一个仅使用 "prev" 和 "next" 链接的自定义分页组件,这很好。但是我需要知道什么时候显示最后一页。现在,我只在传递给 buildQuery()
的 parseResponse()
函数中知道这一点以进行列表查询。此时,我可以访问 nextToken
值。如果它是空的,那么我已经从 AppSync 获取了最后一页结果。如果我可以传递这个值,甚至是一个布尔值,例如lastPage
到自定义分页组件,我已经准备好了。我如何在 React Admin 中执行此操作?
为了实现这一点,我创建了一个自定义缩减器,nextTokenReducer
用于查找 React Admin 的 CRUD_GET_LIST_SUCCESS
操作,其有效负载是来自 AppSync GraphQL 端点的整个响应。我可以从中提取 nextToken
值:
import { CRUD_GET_LIST_SUCCESS } from "react-admin";
export default (previousState = null, { type, payload }) => {
if (type === CRUD_GET_LIST_SUCCESS) {
return payload.nextToken;
}
return previousState;
};
我将这个自定义减速器传递给了我的主要 App
组件中的 Admin
组件:
import nextTokenReducer from "./reducers/nextTokenReducer";
...
class App extends Component {
...
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return <div>Loading</div>;
}
return (
<Admin
customReducers={{ nextToken: nextTokenReducer }}
dataProvider={dataProvider}
>
<Resource name="packs" list={PackList} />
</Admin>
);
}
}
然后我将 nextToken
商店连接到我的自定义分页组件。它会显示 "next"、"prev" 或什么都不显示,这取决于 nextToken
是否在其属性中:
import React from "react";
import Button from "@material-ui/core/Button";
import ChevronLeft from "@material-ui/icons/ChevronLeft";
import ChevronRight from "@material-ui/icons/ChevronRight";
import Toolbar from "@material-ui/core/Toolbar";
import { connect } from "react-redux";
class CustomPagination extends React.Component {
render() {
if (this.props.page === 1 && !this.props.nextToken) {
return null;
}
return (
<Toolbar>
{this.props.page > 1 && (
<Button
color="primary"
key="prev"
icon={<ChevronLeft />}
onClick={() => this.props.setPage(this.props.page - 1)}
>
Prev
</Button>
)}
{this.props.nextToken && (
<Button
color="primary"
key="next"
icon={<ChevronRight />}
onClick={() => this.props.setPage(this.props.page + 1)}
labelposition="before"
>
Next
</Button>
)}
</Toolbar>
);
}
}
const mapStateToProps = state => ({ nextToken: state.nextToken });
export default connect(mapStateToProps)(CustomPagination);
最后,我将自定义分页组件传递到我的列表组件中:
import React from "react";
import { List, Datagrid, DateField, TextField, EditButton } from "react-admin";
import CustomPagination from "./pagination";
export const PackList = props => (
<List {...props} pagination={<CustomPagination />}>
<Datagrid>
...
</Datagrid>
</List>
);
还有一种方法可以使 AppSync 解析器适应 page
和 perPage
本机 react-admin 参数。
这是一个不好的做法,因为查询响应被限制为 1MB,而且还需要为每个页面查询解析和转换完整的 dynamodb 查询响应,但它确实有效。
VTL AppSync 解析器请求映射模板:
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression": "userId = :userId",
"expressionValues" : {
":userId" : $util.dynamodb.toDynamoDBJson($context.identity.sub)
}
}
}
VTL AppSync 解析器响应映射模板:
#set($result = {})
#set($result.items = [])
#set($result.length = $ctx.result.items.size())
#set($start = $ctx.arguments.perPage * ($ctx.arguments.page - 1))
#set($end = $ctx.arguments.perPage * $ctx.arguments.page - 1)
#if($end > $result.length - 1)
#set($end = $result.length - 1)
#end
#if($start <= $result.length - 1 && $start >= 0 )
#set($range = [$start..$end])
#foreach($i in $range)
$util.qr($result.items.add($ctx.result.items[$i]))
#end
#end
$util.toJson($result)
dataProvider.js
...
const buildQuery = () => (
raFetchType,
resourceName,
params
) => {
if (resourceName === "getUserToDos" && raFetchType === "GET_LIST") {
return {
query: gql`
query getUserToDos($perPage: Int!, $page: Int!) {
getUserToDos(perPage: $perPage, page: $page) {
length
items {
todoId
date
...
}
}
}
`,
variables: {
page: params.pagination.page,
perPage: params.pagination.perPage
},
parseResponse: ({ data }) => {
return {
data: data.getUserToDos.items.map(item => {
return { id: item.listingId, ...item };
}),
total: data.getUserToDos.length
};
}
};
}
...