We are having a tree of components which has two separate fragments being passed down:
// SomeComponent.js
fragments: {
customer: () => Relay.QL`
fragment on Customer {
id,
fullName,
...
}
`
viewer: ({customerId}) => Relay.QL`
fragment on Query {
${OtherComponent.getFragment('viewer', {customerId})}
}
`
}
// OtherComponent.js
initialVariables: {
customerId: null
}
fragments: {
viewer: ({customerId}) => Relay.QL`
fragment on Query {
statement(customerId: $customerId) {
edges {
node {
some
fields
}
}
}
}
`
}
The customer fragment is used to just displays a single customer that is fetched via the node(id: $id) query. The other fragment returns a list of statements that can (optionally) be selected just for a single customer. So from the Relay.Route we are passing all the way down the Relay ID for the customer (and on the backend we are converting it to our local id),
So on OtherComponent.js we want to display a single customer's statements which at a glance seems like it should just work.
Strangly though as it stands, when we access this.props.viewer in OtherComponent.js we have an object, but apart from some private properties it is empty.
If now we change the customerId from being passed down to hardcoded (so: ... statement(customerId: valid_global_id) {...) it all of the sudden works. we have all everything on this.props.viewer that we requested.
What is going on here? Is relay trying to do some matching and because it is seeing a valid id for a customer here and we are asking for something else than a customer it just freaks out?
This issue is also very hard to debug as there is no error, we actually get the expected response back it just seems to go 'missing' on the way.
@Globegitter Can you paste the route queries as well? Anything else interesting between the route and the top fragment given above?
We are actually using react-router-relay which injects the params from the route (https://github.com/relay-tools/react-router-relay#path-parameters) and we have that defined as:
const customerNodeQueries = {
customer: () => Relay.QL`query { node(id: $customerId) }`,
viewer: (Component, {customerId}) => Relay.QL`query { viewer(){ ${Component.getFragment('viewer', {customerId})} } }`
};
Nothing else interesting that I can think off. Though I am also just seeing there is a prepareParams in here: https://github.com/relay-tools/react-router-relay#additional-parameters Maybe that would have been a better way of passing down the variable?
@Globegitter Hmm. Should those viewer fragments be fragment on Viewer instead of fragment on Query?
@josephsavona This is a more a graphql python convention: http://graphene-python.org/docs/relay/
Under Node Root field you see that it is defined as class Query which automatically creates it as type Query. There is an example of it: https://github.com/graphql-python/swapi-graphene/blob/master/starwars/schema.py#L147
It has been working fine for us so far for quite a few other queries and we are getting the expected response back from the server (even RelayNetworkDebug is printing that response).
Hmm... this seems familiar. I think we ran into this recently and couldn't figure out why, but walked away from it at that point. The case was very similar:
react-router-relay param)At the time, my suspicion was that the fragment pointer given to the nested container was somehow incorrect, or something... but we had to run and promptly forgot about the issue over the weekend 馃憤
Thanks for the reminder, @NevilleS.
@Globegitter Be sure to pass any overridden variables down in props, so <Child customerId={this.props.relay.variables.customerId} ... />. Does that help?
I got the same issue. I don't understand @josephsavona, he is passing the parameter through the container, so if he were to pass the changed relay variables down he would have to manually pass child relay context from the parent?
When a parent overrides child variables with the second argument to Container.getFragment(), it must also pass the same variables as props to that container. A container might be included by a parent with multiple different values and needs the variables as props to know which variables to use when reading data from the cache.
@josephsavona Yeah that 'double' syntax does initially feel a bit unnatural and verbose. It has tripped up a few people in our office in the past that you need to define Child.getFragment('name') and then also pass down <Child name={this.props.name} ... />.
Couldn't the getFragment syntax be enough so the RelayContainer can inject the necessary props?
@Globegitter Be sure to pass any overridden variables down in props, so
<Child customerId={this.props.relay.variables.customerId} ... />.Does that help?
Thank you for that answer we did indeed not do that. Will try that asap.
@josephsavona That worked, thank you for the clarification
Couldn't the getFragment syntax be enough so the RelayContainer can inject the necessary props?
@globegitter no, unfortunately it is insufficient. The same child fragment might be included with different sets of variables by the same parent. If we tried too pass down the variables e.g. implicitly through the fragment data, there would be no way to tell which set of variables to use. Passing the vars as props is the only way to disambiguate.
Added some docs around that in #1104 which should hopefully make this easier for others in the future.
@josephsavona There is clearly something I do not fully understand then. I thought that all variables are being passed down from the Route. So looking at my example https://github.com/facebook/relay/pull/1104/files#diff-fbdd57764a52a0f91f0970024af6e714R87 it would seem that the only variable that ever could be passed down as limit is 10 even if we would specify the Child fragment multiple times (given it is hardcoded in the Route).
So is the value defined in the Route ignored then? Or can it simply be overridden? And why do we have to specify ${Child.getFragment('viewer', {limit})}, which implies that limit will be passed down from the Route, if we can pass down whatever we want from the actual component anyway? My apologies for these all these questions, but the semantics around passing down variables the container chain still seems a bit mysterious to me.
I think the confusion is that you are focusing only on the case of a top-level RelayContainer. Containers can be children of other containers, and this is where a given child container could be included by a parent twice with different variables.
To clarify the different pieces:
queries: {
foo: () => Relay.QL`
Query Foo { node(id: $id) }
`,
}
Basically relay passes all the route params for you to getFragment.
getFragment, you must also pass them as props too so that the container knows what vars to use when reading data. @Globegitter I think this example nicely illustrates the ambiguity issue.
@josephsavona Ah yeah of course the container can pass its own variables down, that makes total sense. Hmm, but you are saying that route params are being passed down automatically, when we tested it that was not the case. But maybe that is also because https://github.com/relay-tools/react-router-relay does not actually create a Relay.Route under the hood, or maybe it was because it was at the end of a long day. Either way, I will do some more testing understand the principle now. Thank you for the clarification though.
@jardakotesovec Thank you for the example, yep that clears it up completely.
(that basically resolves the issue, I will close it once I got to test it out.)
Hmm, but you are saying that route params are being passed down automatically, when we tested it that was not the case.
Do you have a repro for this? RelayReadyStateRenderer always passes the route params to the root container. If you use the short query format that I gave above, Relay passes all variables to getFragment. If you manually call ${Container.getFragment()}, only the variables that you specify are passed.
@Globegitter were you able to resolve this?
@josephsavona Thanks for checking in on that. We had in the meantime re-written how the query is structured, which solved this problem as well. There is another part in one of our apps though that I wanted to look into where we are also passing down variables in a similar way. Back when we implemented this I was not aware that we had to pass down the variables as props but we still managed to get it to work somehow. So wanted to verify again what we were doing there. That aside this can be closed and thank you for the quick response and good explanation. Has been very educational.
Do you have a repro for this? RelayReadyStateRenderer always passes the route params to the root container. If you use the short query format that I gave above, Relay passes all variables to getFragment. If you manually call ${Container.getFragment()}, only the variables that you specify are passed.
We have needed Route params again and I can confirm that they are being passed down automatically as expected, so that must have just been something on our side then.
Yeah @josephsavona in our other usage we where actually passing down the variable as prop. I was just not consciously aware of that. That completely resolves this then. Thanks again.
Most helpful comment
Thanks for the reminder, @NevilleS.
@Globegitter Be sure to pass any overridden variables down in props, so
<Child customerId={this.props.relay.variables.customerId} ... />. Does that help?