Search code examples
aws-appsyncreact-admin

How to paginate react-admin lists when the total is unknown?


Summary: I can't get a total number of records from my GraphQL endpoint. I only know if I have reached the end of my GraphQL records list when I am parsing the response from the endpoint. How can I make my custom pagination component aware that it's on the last page?

Details: I'm using React Admin with AWS AppSync (GraphQL on DynamoDB) using ra-data-graphql. AppSync can't tell you the total number of records available to a list query, and it also limits the number of records you can return to a 1MB payload. Instead, it includes a nextToken value if there are more records to query, which you can include in subsequent list queries.

I have created a custom pagination component that only uses "prev" and "next" links, which is fine. But I need to know when the last page is displayed. Right now, I only know this in the parseResponse() function that I'm passing in to buildQuery() for the list query. At this point, I have access to the nextToken value. If it's empty, then I have fetched the last page of results from AppSync. If I could pass this value, or even a boolean e.g. lastPage to the custom pagination component, I'd be all set. How can I do this in React Admin?


Solution

  • There is also a way to adapt AppSync resolver to work with page and perPage native react-admin parameters.

    It's a bad practice because query response is limited by 1MB and also full dynamodb query response needs to be parsed and transformed for each page query, however it does the trick.

    VTL AppSync resolver Request Mapping Template:

    {
        "version" : "2017-02-28",
        "operation" : "Query",
        "query" : {
            "expression": "userId = :userId",
            "expressionValues" : {
                ":userId" : $util.dynamodb.toDynamoDBJson($context.identity.sub)
            }
        }
    }
    

    VTL AppSync resolver Response Mapping Template:

    #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
            };
          }
        };
      }
    ...