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)