-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pagination idea: type-specific refetching plugins #26
Comments
@stubailo I'm a big fan of the flexibility of this method. We will be using what we have of cc @johnthepink |
What do you mean by this week? :P that sounds pretty soon! |
I think these kinds of plugins are a great idea! The thing to keep in mind is that we might have to be more careful with garbage collection in the case where newer queries rely on the data from no longer active queries still being in the store. Somehow we'd have to know what data the plugins are extracting from the store. |
BTW this issue blocks on #42 |
Assigning to Martijn to get his feedback! |
Rather than having regex on type, we could do something similar to ESLint rules, which specify which AST nodes they are interested in: https://github.com/apollostack/eslint-plugin-graphql/blob/545fcecc8476a13c8d12291cd7fc8924a366178a/src/index.js#L25 |
Any updates in pagination? I'm using a custom solution, without a very good result. Really hoping to have an Apollo solution to this.
Problems:
Here is the complete gist, and bellow the main parts and some explanation. const query = `
query doSomeQuery(..., $first: Int!, after: String) { # --> must accept these two parameters (after needs to allow null)
viewer {
nest {
myPaginated($first: Int!, after: String) {
edges { # --> data array must be in edges
node {
...
}
}
pageInfo { # --> must have a pageInfo with at least these two fields
hasNextPage
endCursor
}
}
}
}
}
` Now I can abstract the pagination to a custom connector: const variables = (state, ownProps) => ({
// custom variables: will fixed after first call
});
const config = {
name: 'myUniquePaginationName',
query,
path: ['viewer', 'nest', 'myPagination'], // need to now where the pagination part is so I aggregate data
initCount: 10, // $first parameter in initial fetching
addCount: 10, // $first parameter in adicional fetching
variables,
};
const ComponentWithPagination = Pagination(config)(Component); |
We're going to start working on pagination full-time soon, I'll take a deeper look at this then! |
@stubailo is there any progress / recommendations for pagination? |
Ah, sorry, not yet - we needed to do batching first because it was necessary for Galaxy. I'm not sure that the query diffing approach for pagination is the right one, I think I actually prefer something more like |
@stubailo we do! can we schedule a call? |
Let's write a spec for this? Should we rely on the view frameworks to power this experience for us? If so, let's write specs for the view layers we support now |
Hello, After having recently decided to jump on the Apollo ship, I've been teaching myself Apollo. Please see http://sandbox.kāla.com/data-emulation. Was looking the best way to go about pagination and stumbled across this. I've tried out something similar to what @deoqc has done. Do you think is it a good idea to make some sort of a wrapper for this? So that pagination (and other repetitive and often used functions like sort, filter, etc) can be quickly implemented for any data. How are the Apollo developers going to implement pagination? Also, what is the status for stuff like filtering, sorting and searching? Will Apollo have some support for that, or will we have to write our own stuff? See graphql/graphql-relay-js#20 in this context. Thanks! |
Hello again, Here's another solution that works fairly well with RESTful APIs, I have been able to do all sorts of things - sort, filter, sort on particular columns, text-search using this method. Server, import { createApolloServer } from 'meteor/apollo';
import { HTTP } from 'meteor/http';
import _ from 'lodash';
import randomstring from 'randomstring';
import numeral from 'numeral';
import cache from 'memory-cache';
// ----------------------------------------------------------------------- API Adapter
/**
* @summary Adapter, connects to the API
*/
class Adapter {
/**
* @summary constructor, define default stuff
*/
constructor() {
this.configuration = { // Declare default values here or in the resolver
currentPage: '1', // The first query will always show the first page
};
}
/**
* @summary callApi, calls the API
* @param {string} url, the URL
* @returns {object}, the response
*/
callApi(url) {
try {
const apiResponse = HTTP.get(url);
const returnObject = {
count: null,
data: [],
};
_.each(apiResponse.data.results, function (row) {
returnObject.data.push({
id: randomstring.generate(),
name: row.name,
diameter: row.diameter,
rotationPeriod: row.rotation_period,
orbitalPeriod: row.orbital_period,
gravity: row.gravity.replace('standard', '').trim(),
population: (row.population === 'unknown' ? row.population : numeral(parseInt(row.population, 10)).format('0a')),
climate: row.climate,
terrain: row.terrain,
surfaceWater: row.surface_water,
});
});
returnObject.count = apiResponse.data.count;
return returnObject;
} catch (error) {
console.log(error);
return null;
}
}
/**
* @summary configure the API
* @param {object} args, arguments
* @returns {object} this.configuration, returns the configuration
*/
configure(args) {
this.configuration.currentPage = args.currentPage;
// Just an example. Anything can be returned to the client in the conf object
let metaPlanetsTotalRecords = cache.get('metaPlanetsTotalRecords');
if (!metaPlanetsTotalRecords) {
metaPlanetsTotalRecords = this.callApi('http://swapi.co/api/planets/?page=1').count; // Get counts anyhow, this is used client-side to determine how many pages there will be
cache.put('metaPlanetsTotalRecords', metaPlanetsTotalRecords, (60 * 60 * 1000) /* Keep this in memory of one hour */);
}
this.configuration.totalRecords = metaPlanetsTotalRecords;
return this.configuration;
}
/**
* @summary fetch from remote
* @returns {object} data, returns the data from remote API call, or returns null in case of error
*/
fetch() {
return this.callApi(`http://swapi.co/api/planets/?page=${this.configuration.currentPage}`).data;
}
}
const API = new Adapter();
// ----------------------------------------------------------------------- Schema
const schema = [`
type Planet {
id: String
name: String
diameter: String
gravity: String
climate: String
terrain: String
rotationPeriod: String
population: String
orbitalPeriod: String
surfaceWater: String
}
type MetaPlanets {
planets: [Planet]
totalRecords: String
currentPage: String
}
type Query {
planets: [Planet]
metaPlanets(currentPage: String): MetaPlanets
}
schema {
query: Query
}
`];
// ----------------------------------------------------------------------- Resolvers
const resolvers = {
Query: {
planets() {
return API.fetch();
},
metaPlanets(root, { currentPage = '1' } = {}) { // Declare default values here or in the Adapter
return API.configure({ currentPage });
},
},
MetaPlanets: {
planets() {
return API.fetch();
},
},
};
createApolloServer({
graphiql: true,
pretty: true,
schema,
resolvers,
}); And, in client, import React from 'react';
import { connect } from 'react-apollo';
import { createContainer } from 'meteor/react-meteor-data';
import gql from 'graphql-tag';
import { Table } from 'meteor/sandbox:lib-duplicate';
// ----------------------------------------------------------------------- Component JSS Stylesheet
... // Removed for brevity
// ----------------------------------------------------------------------- Table definitions
const columns = [{
title: 'Name',
key: 'name',
render: (record) => {
return (
<span>{record.name}</span>
);
},
}, ... // Removed for brevity];
// ----------------------------------------------------------------------- Component
/**
* @summary DataEmulationPaginate
* @returns {object} Renders a DOM Object
*/
class DataEmulationPaginate extends React.Component {
/**
* @summary constructor function
* @param {object} props, component properties
* @returns {object} null
*/
constructor(props) {
super(props);
// Link functions
this.onPageChange = this.onPageChange.bind(this);
}
/**
* @summary onPageChange function, what to do on page change?
* @returns {object} null
*/
onPageChange(page) {
console.log(`On page: ${page}`);
}
/**
* @summary render function
* @returns {object} Returns DOM Object
*/
render() {
const classes = this.props.sheet.classes;
console.log(this.props);
console.log(this.props.data.metaPlanets);
return (
<section>
<h1 className="m-b-1">Paginate</h1>
<Table columns={columns} dataSource={this.props.data.loading === false ? this.props.data.metaPlanets.planets : []} loading={this.props.data.loading} pagination={{ total: 61, onChange: this.onPageChange }} />
</section>
);
}
}
// ----------------------------------------------------------------------- GraphQL Adapter
const Adapter = connect({
mapQueriesToProps() {
return {
data: {
query: gql`
query getMetaPlanets ($currentPage: String) {
metaPlanets(currentPage: $currentPage) {
currentPage
totalRecords
planets {
id
name
diameter
rotationPeriod
orbitalPeriod
gravity
population
climate
terrain
surfaceWater
}
}
}
`,
variables: {
currentPage: '2', // Connect this to state or props, or whatever
},
},
};
},
})(DataEmulationPaginate);
// ----------------------------------------------------------------------- Container Component
const Container = createContainer(() => {
return {};
}, Adapter);
export default useSheet(Container, stylesheet); |
Here's a working example, Apollo-Paginate |
@dbx834 Seems to be offline. |
Please try again. Just checked, it works for me. |
Yes, seems to be up again, thanks! :)
|
We just merged a new pagination approach: #472 Docs coming soon: https://github.com/apollostack/docs/pull/157 |
* chore(docs): Angular2 docs * fix(layout): Fix too many underlines...
* Basic redux docs * Added some notes about the dev tools extension
In some sense, handling pagination in a smart way is just a special case of "when you ask for data, treat certain fields in a special way because we know that the arguments are meaningful." So if we're looking at a Relay spec paginated list:
Notice how easy it is for us as humans to imagine what data needs to be fetched to satisfy the new query.
Here's a set of hypotheses:
__typename
fields where necessary.Basically, you could write a function and tell the apollo client:
"When refetching any field which refers to an object with a type matching this regular expression, give me the query you were going to fetch, and the contents of the store, and I'll give you a new query that says how to fetch the missing data."
So, for Relay pagination, you'd say:
Ideally this will allow plugging in to different pagination systems, as long as the paginated fields have predictable type names, for example
*PaginatedList
orPaginated*
.If we can do this, it will achieve some really nice effects:
This is just a very ambitious idea, and I'm eager to determine if this very simple model can actually handle all cases of pagination and Relay connections in particular. More analysis to come soon.
@jbaxleyiii @helfer curious what you think about this.
The text was updated successfully, but these errors were encountered: