After some challenges, I've finally added pagination in the application.
That required me to add DataLoader, ModernQueryRenderer and RefetchContainer.
I still can't explain in details everything I've done, but I'll share the files I've changed.
// EventList.js import { createRefetchContainer, graphql, } from 'react-relay'; // ... <View style={styles.container}> <FlatList data={events.edges} renderItem={renderItem} keyExtractor={item => item.node.id} onEndReached={onEndReached} onRefresh={onRefresh} refreshing={isFetchingTop} ItemSeparatorComponent={() => <View style={styles.separator} />} ListFooterComponent={null} /> </View> // ... const EventListPaginationContainer = createRefetchContainer( EventList, { query: graphql` fragment EventList_query on Query @argumentDefinitions( count: {type: "Int", defaultValue: 10} cursor: {type: "String"} ) { events(first: $count, after: $cursor) @connection(key: "EventList_events") { pageInfo { hasNextPage endCursor } edges { node { id title date description author } } } } `, }, graphql` query EventListPaginationQuery($count: Int!, $cursor: String) { ...EventList_query @arguments(count: $count, cursor: $cursor) } `, ); // ... export default createQueryRendererModern( EventListPaginationContainer, EventList, { query: graphql` query EventListQuery($count: Int!, $cursor: String) { ...EventList_query } `, variables: {cursor: null, count: 5}, }, );
We've updated the EventList to a display a FlatList component and to use a createQueryRendererModern and a createRefetchContainer.
// createQueryRendererModern.js import * as React from 'react'; import {Text} from 'react-native'; import {QueryRenderer} from 'react-relay'; import Environment from './Environment'; export default function createQueryRenderer(FragmentComponent, Component, config) { const {query, queriesParams} = config; class QueryRendererWrapper extends React.Component { render() { const variables = queriesParams ? queriesParams(this.props) : config.variables; return ( <QueryRenderer environment={Environment} query={query} variables={variables} render={({error, props}) => { if (error) { return <Text>{error.toString()}</Text>; } if (props) { return <FragmentComponent {...this.props} query={props} />; } return <Text>loading</Text>; }} /> ); } } return QueryRendererWrapper; }
In the server's side we've need to add dataloaders in the app's context and use them to load events from Mongoose. We're also using graphql-mongoose-loader to abstract the interactions when loading data from MongoDB.
// server/app.js // ... const graphqlSettingsPerReq = async req => { const { currentUser } = await getUser(req.header.authorization); const dataloaders = Object.keys(loaders).reduce( (acc, loaderKey) => ({ ...acc, [loaderKey]: loaders[loaderKey].getLoader(), }), {}, ); return { schema, context: { currentUser, req, dataloaders } }; }; // ...
// EventLoader.js import DataLoader from 'dataloader'; import { connectionFromMongoCursor, mongooseLoader } from '@entria/graphql-mongoose-loader'; import { ConnectionArguments } from 'graphql-relay'; import EventModel, { IEvent } from './EventModel'; export default class Event { constructor(data) { this.id = data.id || data._id; this._id = data._id; this.title = data.title; this.description = data.description; this.author = data.author; } } export const getLoader = () => new DataLoader(ids => mongooseLoader(EventModel, ids)); const viewerCanSee = () => true; export const load = async (context, id) => { if (!id) { return null; } let data; try { data = await context.dataloaders.EventLoader.load(id); } catch (err) { console.log(err) return null; } return viewerCanSee() ? new Event(data, context) : null; }; export const clearCache = ({ dataloaders }, id) => dataloaders.EventLoader.clear(id.toString()); export const primeCache = ({ dataloaders }, id, data) => dataloaders.EventLoader.prime(id.toString(), data); export const clearAndPrimeCache = (context, id, data) => clearCache(context, id) && primeCache(context, id, data); export const loadEvents = async (context, args) => { const where = args.search ? { title: { $regex: new RegExp(`^${args.search}`, 'ig') } } : {}; const event = EventModel.find(where).sort({ createdAt: -1 }); return connectionFromMongoCursor({ cursor: event, context, args, loader: load, }); };
// QueryType.js export default new GraphQLObjectType({ name: "Query", description: "The root of all... queries", fields: () => ({ node: nodeField, // ... }, events: { type: EventConnection.connectionType, args: { ...connectionArgs, search: { type: GraphQLString } }, resolve: (obj, args, context) => { return EventLoader.loadEvents(context, args)}, }, // ... } }) });
This should be enough to enable pagination.
Remember to run yarn update-schema
and yarn relay
to update the schema and generated files respectively
Top comments (0)