当总数未知时如何对 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 解析器适应 pageperPage 本机 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
        };
      }
    };
  }
...