Giter Club home page Giter Club logo

Comments (13)

leebyron avatar leebyron commented on May 1, 2024 15

We don't support generic types like Page<T>, as representing them client-side can be really tricky. Is it a template type? Is it a generic interface? Lots of questions and complexity we chose to avoid, at least for now.

@devknoll, you're pretty much on the money with that answer. We have a convention that we use at Facebook that we call Connection. We have some helper functions on the server side that keeps us from duplicating the work of implementing this convention, and avoids mistakes. That gets you most of the value you would get from having some kind of template type or generics.

At Facebook, it looks like this:

type UserConnection {
  count: Int
  nodes: [User]
  edges: [UserConnectionEdge]
}

type UserConnectionEdge {
  node: User
  cursor: String
}

You can imagine replacing User with any type here and the type model still works. The nodes field is a shorthand for edges { node { ... } } when all you want are the nodes at the end of the connection. edges also has cursor as a utility for performing pagination.

Pagination in GraphQL at Facebook looks something like:

type User {
...
friends(after: String, first: Int, before: String, last: Int): UserConnection
...
}

So here, the friends field on a User accepts a couple arguments. after accepts a value that cursor gave you in a prior query, and first limits how many items are returned. before and last are the same ideas, but for paginating from the back of a list towards the front.

So again, this is just a convention that Facebook uses. GraphQL itself doesn't know what pagination is, it just enables patterns like these. Underlying code is responsible for actually reading these arguments and applying them in a sensible way.


You could also imagine a much simpler pagination model that just does offset, limit. There are some issues with this approach (for example, if the front of the list changes often, or when deep into a very long list), but it's simplicity is compelling.

In this model, you might instead type things as:

type User {
...
friends(offset: Int, limit: Int): [User]
...
}

Now there's no additional Connection type at all; I would suggest that offset and limit work here just as they do in SQL.

from graphql-spec.

leebyron avatar leebyron commented on May 1, 2024 3

@RavenHursT, ensure that your variable is not required.

For example, this query should error if no value is provided for $after since it's required to be a non-null string (String!).

query Example($after: String!) { 
  someConnection(after: $after) { count }
}

Where this example which accepts null (just String), should not error if no value is provided for $after. This is considered best practice for using variables with pagination.

query Example($after: String) { 
  someConnection(after: $after) { count }
}

from graphql-spec.

graue avatar graue commented on May 1, 2024 2

@leebyron What do you think about documenting that pattern somewhere more visible than this issue?

from graphql-spec.

devknoll avatar devknoll commented on May 1, 2024

As far as what I've seen in the past on GraphQL, if you wanted pagination, one schema you could use to achieve that would be:

type FriendList {
  count: Int,
  edges: [User!]
}

type User {
  friends(first: Int, after: ID): FriendList
}

This should hopefully answer both of your questions. Pagination would be implemented by using the first and after field arguments on friends, and the count accessed would be on the type FriendList, a wrapper over the list of Users.

I'm not entirely sure if this has been changed, so I would love to see @leebyron pitch in too 😄

from graphql-spec.

luisobo avatar luisobo commented on May 1, 2024

@devknoll thanks for your response. Yup, I guess something among those lines is always possible. There are some caveats with an approach is that you'd have to duplicate the FriendList type for every type you want to paginate over PostList etc.

This relates to the question I mentioned, whether or not is possible to create generic types just like the native List.

from graphql-spec.

luisobo avatar luisobo commented on May 1, 2024

💃

@leebyron thanks! I'll play around with this ideas. Thank you!

from graphql-spec.

dschafer avatar dschafer commented on May 1, 2024

@graue Yep, we're definitely planning on doing so!

from graphql-spec.

luisobo avatar luisobo commented on May 1, 2024

@leebyron I've been playing with this and it works great. The duplication in the server is not a problem as you mentioned, a function to define a connection type for a given types solves the problem just fine.

I have a couple of follow up questions and I was hoping you could shed some light:

type UserConnectionEdge {
  node: User
  cursor: String
}
  • why do you guys put the cursor of every node along with that node? What's the use case behind this decision? Wouldn't it be enough to have a before and after (first and last item) cursor at the connection level?
  • why do you call it connection? I'm just trying to understand the abstraction properly. I've been thinking about it in terms of pages. I'm wondering if there are any caveats.
  • I'm thinking to implement pagination like this. I'd love feedback.
type UserPage {
  count: Int
  nodes: [User]
  after: String
  before: String
}

and the entry point would be the same

type User {
...
friends(offset: Int, limit: Int): [User]
...
}

Thank you so much in advance.

from graphql-spec.

RavenHursT avatar RavenHursT commented on May 1, 2024

@leebyron Thanks for your informational reply. It's helped a lot! So sorry to revive this old thread.. but I'm completely lost as to how to implement one piece of what you said:

after accepts a value that cursor gave you in a prior query

Ok.. and I've definitely gotten that to work by taking a look at the edge cursors on the /graphql response in my browser. Problem is.. how do I make this work for the first "page" of items, when I have no "prior query" to work from?

I tried leaving after as undefined in my query, but I only got the following error:

Uncaught Invariant Violation: callsFromGraphQL(): Expected a declared value for variable, $curs.

It seems, if you define first and after in your container's fragment, then they're required parameters. But I have no value for after, so how in the world does one initialize this?

Thanks!

from graphql-spec.

RavenHursT avatar RavenHursT commented on May 1, 2024

@leebyron yeah.. turns out that you have to explicitly set default variables w/ null, not just allow them to be undefined:

http://stackoverflow.com/questions/34406667/relay-pagination-how-to-initialize-after-value/34407181#34407181

Thanks.

from graphql-spec.

leebyron avatar leebyron commented on May 1, 2024

That sounds like a bug actually. What GraphQL library are you using? graph-js?

from graphql-spec.

RavenHursT avatar RavenHursT commented on May 1, 2024

We're using express-graphql

from graphql-spec.

caub avatar caub commented on May 1, 2024

We don't support generic types like Page<T>

well it's working: https://launchpad.graphql.com/nxqjv8k4l7

With this Page template type for example:

function Page(Type) {
	return new ObjectType({
		name: `Page${Type}`,
		description: `A simple pagination method
 - nodes contains the actual list of data
 - cursor is the end cursor or falsy if there are no more pages`,
		fields: {
			nodes: {type: new NonNull(new List(new NonNull(Type)))},
			cursor: {type: String},
		},
	});
}
const QueryType = new ObjectType({
	name: 'Query',
	fields: {
		foos: {
			type: Page(Foo),
			args: listArgs,
			resolve: (_, args) =>getFoos(args),
		},
	},
});

from graphql-spec.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.