Reputation: 145
I'm having trouble resolving graphql nested types. I can successfully get the UserMetrics
nested resolver to fire, but the parent resolver object (user) is null. Am I misunderstanding the GraphQL resolver map?
Schema:
type User {
id: String!
metrics: UserMetrics
}
type UserMetrics {
lastLogin: String!
}
Resolver:
Query: {
user(_, { id }, ctx) {
return { id };
}
},
User: {
metrics(): ({}), // UserMetrics.lastLogin doesn't fire without this
},
UserMetrics: {
lastLogin(user) {
console.log(user); // null
}
},
Upvotes: 13
Views: 11831
Reputation: 84687
This can be a little confusing to wrap your head around, and unfortunately the docs don't provide a solid example of how nested types work. According to the docs, the first argument the resolver takes is:
The object that contains the result returned from the resolver on the parent field
So, the parent (User) resolves a certain field (metrics). If this field is a scalar, it doesn't need to do anything else and that's what's returned. Otherwise the value the parent resolved is passed down to the resolvers associated with the "child" type (UserMetrics). Each of those resolvers then uses that value to resolve the fields it is responsible for.
In the code you provided, User
is resolving metrics
as an empty object (i.e. {}). This is the value that's passed to the resolver for any field inside UserMetrics. Strictly speaking, in this case, running console.log on that value will return {}, not null.
Try this: Comment out your resolver for the metrics
field, and instead of { id }
, have your query return { id, metrics: { lastLogin: 'foo' }}
. Run that and you should see a console output showing the object that was passed to the resolver for lastLogin
, in this case { lastLogin: 'foo' }
.
Step by step, what's happening here:
user
field of the query is resolving to { id: 'whateverIdYouProvided', metrics: { lastLogin: 'foo' }}. Since it's returning a User
type, and not a scalar, it passes this value down to the resolvers for any fields inside User
.id
has no resolver specified in this case, so it uses the default resolver. This just looks at the object it received, and if it finds a property that matches the name of its field, it resolves to the value of that property. If it can't find any property by that name, it returns null. In this case, id is a scalar, so it just resolves to 'whateverIdYouProvided' and doesn't need to go any further.metrics
resolver, this field will also use the default resolver. In this case, it resolves to a type (UserMetrics), so it looks at the object it was given, finds a property called metrics
and passes that value (i.e. { lastLogin: 'foo' }
) to any resolvers for fields inside UserMetrics.{ lastLogin: 'foo' }
-- to work with. If you don't specify a resolver here, again the default resolver will be used and it will happily find a property called lastLogin and resolve to it's value ('foo'). If you provide a custom resolver, as you have done, obviously it will resolve to whatever you return (in the case of your code, null).If the lastLogin
resolver isn't being called at all, verify that you've actually included that field in your query! Any fields not included in the query don't need to be resolved, and so their resolvers aren't called.
Upvotes: 17