1
votes

Comment paginer les listes react-admin lorsque le total est inconnu?

Résumé : je ne parviens pas à obtenir un nombre total d'enregistrements à partir de mon point de terminaison GraphQL. Je sais seulement si j'ai atteint la fin de ma liste d'enregistrements GraphQL lorsque j'analyse la réponse du point de terminaison. Comment puis-je faire savoir à mon composant de pagination personnalisé qu'il se trouve sur la dernière page?

Détails : j'utilise React Admin avec AWS AppSync (GraphQL sur DynamoDB) en utilisant ra-data-graphql . AppSync ne peut pas vous indiquer le nombre total d'enregistrements disponibles pour une requête de liste et limite également le nombre d'enregistrements que vous pouvez renvoyer à une charge utile de 1 Mo. Au lieu de cela, il inclut une valeur nextToken s'il y a plus d'enregistrements à interroger, que vous pouvez inclure dans les requêtes de liste suivantes.

J'ai créé un composant de pagination personnalisé qui n'utilise que les liens "prev" et "next", ce qui est très bien. Mais j'ai besoin de savoir quand la dernière page est affichée. Pour le moment, je ne le sais que dans la fonction parseResponse () que je transmets à buildQuery () pour la requête de liste. À ce stade, j'ai accès à la valeur nextToken . S'il est vide, j'ai récupéré la dernière page de résultats d'AppSync. Si je pouvais passer cette valeur, ou même un booléen, par exemple lastPage au composant de pagination personnalisé, je serais prêt. Comment puis-je faire cela dans React Admin?


1 commentaires

En regardant la documentation , en utilisant customReducers pourrait être le chemin à parcourir.


3 Réponses :


2
votes

Pour ce faire, j'ai créé un réducteur personnalisé, nextTokenReducer , qui recherche l'action CRUD_GET_LIST_SUCCESS de React Admin, dont la charge utile est la réponse complète du point de terminaison AppSync GraphQL. Je peux extraire la valeur nextToken de cela:

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>
);

J'ai transmis ce réducteur personnalisé au composant Admin dans mon principal Composant App :

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);


J'ai ensuite connecté la boutique nextToken à mon composant de pagination personnalisé. Il affichera "next", "prev", ou rien selon que nextToken est dans ses accessoires:

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>
    );
  }
}

Enfin, j'ai passé la coutume composant de pagination dans mon composant de liste:

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


3 commentaires

Comment puis-je augmenter la limite de requête sur une ressource?


@Berni, vous ne pouvez pas . "Une seule opération Query peut récupérer un maximum de 1 Mo de données. Cette limite s'applique avant que toute FilterExpression ne soit appliquée aux résultats. Si LastEvaluatedKey est présent dans la réponse et n'est pas nul, vous devrez paginer l'ensemble de résultats (voir Pagination des résultats). "


C'est la meilleure réponse pour appsync / dynamodb / amplify. jouer avec VTL, scanner, essayer de faire ce que dynamodb n'a pas été conçu pour faire, c'est l'odeur, l'OMI. Vive la bonne solution.



2
votes

Il existe également un moyen d'adapter le résolveur AppSync pour qu'il fonctionne avec les paramètres natifs react-admin page et perPage .

C'est une mauvaise pratique car la réponse à la requête est limitée à 1 Mo et la réponse complète à la requête dynamodb doit être analysée et transformée pour chaque requête de page, mais cela fait l'affaire.

Modèle de mappage de demande de résolution de VTL AppSync :

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

Modèle de mappage de réponse du résolveur 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

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


1 commentaires

J'ai changé la réponse acceptée à cela. La solution que j'ai publiée était maladroite et a donné des résultats imprévisibles lors du passage de la création / modification à la liste. Cela dit, j'ai choisi de ne pas paginer du tout, car le nombre d'enregistrements n'était pas trop grand et les filtres RA fournissent une UX adéquate.



0
votes

Dans DynamoDB, vous pouvez interroger le nombre total avec une (seconde) requête Scan .

Étant donné que vous utilisez un schéma similaire:

query allPosts($page: Int, $perPage: Int, $sortField: String, $sortOrder: String, $filter: ServiceFilter) {
  items: allPosts(page: $page, perPage: $perPage, sortField: $sortField, sortOrder: $sortOrder, filter: $filter) {
    ...
  }
  total: _allPostsMeta(page: $page, perPage: $perPage, filter: $filter) {
    count
  }
}

Vous pouvez créer un résolveur pour Query._allPostsMeta avec ces modèles VTL:

Demander un modèle VTL:

#set($result = {"count": $ctx.result.scannedCount})
$util.toJson($result)

Résoudre le modèle VTL:

{
    "version" : "2017-02-28", 
    "operation" : "Scan",
    "select": "COUNT"
}

Requête React-admin 'GET_LIST':

type Query {
  Post(id: ID!): Post
  allPosts(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): [Post]
  _allPostsMeta(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): ListMetadata
}

type ListMetadata {
    count: Int!
}
...

Cette approche est utilisée dans ra-data-graphql-simple


1 commentaires

Faire une analyse complète de la table est une idée terrible - cela coûte cher (littéralement) et pourrait impliquer la pagination de nombreux ensembles de résultats de données.