Hello. Right now, from what I can tell, you can use inline fragments nested inside a query, like so:
const query = gql`
query {
me {
id
... on User {
slug
email
}
}
}
`
But you cannot write a reusable gql string with an inline fragment at the top level, like so:
const fragment = gql`
... on User {
slug
email
}
`
Because you'll end up with an error:
GraphQLError: "Syntax Error: Unexpected ..."
I think this should be supported. (Even if it requires gql.fragment or a similar helper.) The reason is that it makes colocation of queries/fragments with components much less verbose, and eliminates the need for them to agree on a magic fragment name that has to be hardcoded between them.
Right now you're forced do do:
const query = gql`
query {
me {
id
...AnotherComponentUser
}
${AnotherComponent.fragments.user}
}
`
But it should be possible to simply do this instead:
const query = gql`
query {
me {
id
${AnotherComponent.fragments.user}
}
}
`
This eliminates the magic AnotherComponentUser fragment name which is hardcoded between the components and hard to keep in sync or notice when mismatching.
graphql-tag is just a light wrapper around graphql-js, so a lot of this comes down to "what is a valid GraphQL document". we validate every document / operation separately, and since the interpolation of fragments happens at runtime, we don't have much control over whether or not its a _completely_ valid document. thus, every invocation of gql is itself a GraphQL document
the alternate solution here is just to go ahead and use string interpolation, since the query you constructed will get created at runtime anyway.
closing because i dont have a clear action item here.
Fair enough, thanks for the response. I guess I鈥檒l just stick with interpolating strings instead.
@leebyron, it might be nice to be able to define anonymous fragments as a top-level piece of a document for composability and tooling reasons.
Fragments need a name so they can be referenced where they鈥檙e used.
In general I would discourage using template literal interpolation with GraphQL unless a build-time compiler can enforce guarantees and replace with static strings at runtime. Runtime interpolation runs the risk of query injection attacks.
As far as I鈥檓 aware, graphql-tag does not have this build-time step. If it did then I could see it supporting this non-standard syntax, however the core GraphQL syntax (supported by GraphQL.js) won鈥檛 support anonymous top level fragments since there is not a generic use case for them.
Fragments need a name so they can be referenced where they鈥檙e used.
Not sure why though? From what I can tell, anonymous fragments can be used anywhere a named fragment can鈥攁t least from my quick experimentation. You can do something like:
user(id: "1") {
id
... on User {
name
email
}
}
And that works just fine?
In general I would discourage using template literal interpolation with GraphQL unless a build-time compiler can enforce guarantees and replace with static strings at runtime. Runtime interpolation runs the risk of query injection attacks.
What's the attack vector here? If the GraphQL API is already a public-facing API then users can already perform any queries they want against it. Not to mention that in this case the only interpolation that happens is putting other static strings inside a query?
... however the core GraphQL syntax (supported by GraphQL.js) won鈥檛 support anonymous top level fragments since there is not a generic use case for them.
That's what I'm trying to argue for. There is definitely a use case for this.
Colocating queries is one of the biggest benefits GraphQL provides, with the goal of reducing the coupling between components. But right now both Apollo and Relay require that you name your fragments in a way that they are hardcoded in the parent. For example, from the Relay docs:
fragment TodoList_list on TodoList {
# Specify any fields required by '<TodoList>' itself.
title
# Include a reference to the fragment from the child component.
todoItems {
...TodoItem_item
}
}
If you ever change TodoItem_item in your child component, you'll break your app in an unintuitive way.
If however, there was a way to define reusable anonymous fragments in GraphQL, these libraries wouldn't need to be designed to be used in these tightly coupled ways. It wouldn't necessarily need to be "top-level", but it could be a "partial" document if needed. And then you could instead do:
const query = gql`
fragment TodoList_list on TodoList {
# Specify any fields required by '<TodoList>' itself.
title
# Include a reference to the fragment from the child component.
todoItems {
${TodoItem.fragments.item}
}
}
`
...where the reference is not made by a brittle, hardcoded string.
Does that make sense? Am I missing something?
@leebyron @jnwng FWIW, this new library fraql is pretty much doing what I describe here, by allowing people to convert fragments into nameless inline fragments so they can be easily dropped into existing queries.
If there's no support from graphql.js I guess this has to be a third-party library like this, and I'm happy to use it exclusively.
But to me it seems like something that shouldn't need to shard the ecosystem... the use case of co-locating fragments with components is so core to GraphQL (one of Relay's main selling points). In fact, from the FraQL docs:
Puting data next to your component is a good practice. It is built-in Relay and Lee Byron explains the advantages into his talk about the IDEA architecture. If you use Apollo or other GraphQL client, it is not an easy task. The goal of FraQL is to make it simple and easy to do in all your applications (React, VueJS...). Since GraphQL is statically typed, FraQL can also generate mock from fragments!
To me this seems like a place the standard tag could be doing better.
I think there are two solutions, either support could be top-level:
const fragment = gql`
... on User {
slug
email
}
`
Or if it's decided that there are too many issues with typing, etc. then it could be added as a separate but easy-to-dip-into concept:
const fragment = gql.fragment`
... on User {
slug
email
}
`
The only thing that's important to remember with the separate gql.fragment is that it be able to handle multiple inline fragments at once, like:
const actorFragment = gql.fragment`
... on User {
slug
email
}
... on Bot {
slug
id
}
Most helpful comment
@leebyron @jnwng FWIW, this new library
fraqlis pretty much doing what I describe here, by allowing people to convert fragments into nameless inline fragments so they can be easily dropped into existing queries.If there's no support from
graphql.jsI guess this has to be a third-party library like this, and I'm happy to use it exclusively.But to me it seems like something that shouldn't need to shard the ecosystem... the use case of co-locating fragments with components is so core to GraphQL (one of Relay's main selling points). In fact, from the FraQL docs:
To me this seems like a place the standard tag could be doing better.