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?
3 Réponses :
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; };
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.
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.
... 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 }; } }; } ...
#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)
{ "version" : "2017-02-28", "operation" : "Query", "query" : { "expression": "userId = :userId", "expressionValues" : { ":userId" : $util.dynamodb.toDynamoDBJson($context.identity.sub) } } }
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.
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
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.
En regardant la documentation , en utilisant
customReducers
pourrait être le chemin à parcourir.