-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
[WIP] Fetch more / Infinite Scroll #335
Conversation
I know that merging that won't be a piece of 🍰 ... I am prepared to work with you to integrate that correctly. |
This looks like a really nice approach, and has huge potential! Thank you for giving it so much thought. I just have a few questions:
This is definitely something we want to use in a production app. @timbotnik, can you chime in here, would this work for the Galaxy stuff? |
|
@stubailo: I updated my PR TODO accordingly, can you tell me if that's ok for you as a PR roadmap ? |
The TODO looks good! I'll try to take a look at the code soon, haven't done that yet, so I can't confirm if the implementation approach is correct. |
@stubailo: about 2., I thought about filtering by |
About 4., I'll take the existing vocabulary, that seems great! |
This is pretty cool @rricard. Some thoughts:
So the point of all that rambling was instead of
I'm sure this would be going beyond the scope of this PR, but it might inform something about the API / naming that we choose. |
@timbotnik Interesting ...
For the global |
Good points @rricard - re: sort, wouldn't the same apply to the APPEND vs PREPEND argument? More concretely, could you have 2 instances of |
I would not associate the APPEND/PREPEND with the query but with the |
But contrarily to what Relay does, Apollo works more in a configuration over convention manner when Relay seems to work more like magic (but enforces a strict schema form when Apollo does not). And for me, that was the point that made us adopt Apollo over relay. |
I agree, let's start with local configuration, and we can add global pagination config later if it turns out to be helpful.
Perhaps instead of // Simple append
addResults: (existing, new) => {
return existing.concat(new);
}
// Append, then sort by timestamp
addResults: (existing, new) => {
return existing.concat(new).sort((a, b) => { return a.timestamp - b.timestamp });
}
// etc. I agree with Tim that pagination isn't uniform. If you couldn't do this here, I suppose you could easily do it in your React component: function MyComponent(props) {
// Sort props before rendering
const data = props.data.myArray.sort((a, b) => { return a.timestamp - b.timestamp });
return <div></div>;
} @timbotnik the above doesn't seem that bad, I wouldn't consider that to be a "duplicate cache". However, @rricard even if your server is handling the ordering, you still need append vs. prepend, because sometimes one needs to paginate from the bottom of the list up (like in the chat usecase, but I suppose you could send the timestamp anyway). Store insertionAnother thing that we should think about, which I think isn't too complex, is how the caching should behave in this case. Let's say I do the following:
Sounds like in this implementation, (3) will now return me 20 items instead of the 10 I ask for, because the pagination parameters will be ignored when reading from the cache? This is leading me to think that for Relay, |
Oh, another concern I have when looking at the implementation - it looks like when using |
@stubailo need to move now, will respond to your concerns a bit later... |
@@ -66,7 +66,7 @@ export function writeFragmentToStore({ | |||
variables?: Object, | |||
dataIdFromObject?: IdGetter, | |||
quietFields?: string[], | |||
fetchMore?: boolean, | |||
fetchMore?: 'APPEND'|'PREPEND', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In TypeScript, you could define a type for this to avoid duplicating this everywhere, like:
type FetchMoreMode = 'APPEND'|'PREPEND';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's what I would like to do! But I'm still not sure in which file to put it ;)
Thanks again for doing this! Really excited about having a pretty simple yet flexible pagination model. |
Ok, I have a PR over at React to finish first, then back here! |
@stubailo: So, here's what I did today first:
Ok, now, followup to your comments:
|
So my main concerns right now are the APIs:
|
I pretty much agree with most of your conclusions. Some comments:
We could provide some basics, but I think if it's only 3 lines of code to implement it would be more confusing than helpful. (like if it's literally
I'm a bit confused about this. Like imagine this situation:
If I want to load 10 more todos, how do I make sure that the |
Yes, I do agree, let's just keep it linked to the type! |
I'd make it a flat object of options:
Perhaps there can be some way of specifying which arrays we are actually interested in appending? Maybe that's the job of Like maybe addResults just gets the complete first result, and the complete second result, and then you merge arrays as necessary? |
Let's freeze the APIs. I propose something type-based so we avoid any weird stuff.
|
With a flat arg, we could override the query's opts from the fetchMore
|
It looks like with the proposed API it will actually be able to do that? Also, does mergeResults get the arrays in the results that match Wait guys I have an idea - what if we just used a directive in the query to specify which fields we want to paginate on? Like this:
That way, it's totally unambiguous which array we are fetching more of. |
That is very cool. I'll need some help there still, I know how to access directives in the backend (via the ast) but I'm not sure how to that on the client... Also why the names argument and not a quietArgs instead? |
@rricard Oh, this is just to annotate that part of the query with a name - so when you call |
Will need to use it during the resolution algorithm
The concatenation is performed differently now. First, we diff against the store, the result are the value currently in the store that are not in the returned results! That way we can concat the values the way we want!
I don't know why but I replaced with quietFields. quietArguments is better
It will be used to point where the pagination should happen in a query. It is properly tested. However the validation system should be redone at some point.
An use case where fetchMore is internally just a boolean!
Now using a flat args configuration
mergeResults will define how to merge paginated lists targetedFetchMoreDirectives will be able to target specific fetchMore directives (by default, confider all directives)
We need to correctly carry mergeResults & targetedFetchMoreDirectives to the writeToStore context
Failing tests for now
For now, only the un-named directive passes
Now we can just target a named directive to perform the concatenation.
Global & named!
That way you can write very cool queries with everything you need inside!
We check for args
This is way much more accurate and efficient and based on actual Directives injectable into real schemas!
Used to do much perform much simpler logic as it is detached from the validation and resolves variables as well.
We lose easily a few lines by doing that!
We reproduce a much lightweight version of the GraphQL types to avoid importing too much stuff.
@rricard if you look in the package.json you should see the size limit, I think we should be good to bump that for this great feature! Also, when this is merged, I'll add I'm really looking forward / need this! Thanks so much for the great work! |
This is looking really good so far! I'm going to take an in-depth look today. @rricard, I added you as a contributor to this repository. Do you mind re-submitting this as a branch from this repo so that I can also push commits to it? That way I can make small changes directly, instead of having to make another fork/PR. Just link back to this for the conversation, and maybe copy the PR description into the new one. |
@stubailo: yes I can do that, thanks for making me contributor! |
Closing for a followup pr in the apollostack repo |
Adds a fetchMore method able to fetch new data and concatenate it into the store, performing, that way, an infinite scroll behavior!
TODO:
concatPaths: Array<string>
topaginationParameters: Array<string>
, that way suppressing for special paths those parameters from the store ids. (ex: instead ofROOT_QUERY.allPosts({cursor: 'x', type: 'y'})
, ifpaginationParameters: {'ROOT_QUERY.allPosts': ['cursor']}
is set, it will only beROOT_QUERY.allPosts({type: 'y'})
paginationParameters
=>quietArguments
append/prepend/sort
fetchMore()
to the public API,paginationParameters
everytime there is some store access & `fetchMore everytime we're doing a query)paginationParameters
in account.graphql-tools
)Basic usage example:
Advanced usage example:
Advanced usage example with directives only!: