How to pass root parameters in the resolver function of a nested query? - reactjs

I have a query of the following nature
Category1(name: $cat1){
Category2(secondName: $cat2){
secondName
}}
My schema is like so:
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
Category1: {
type: new GraphQLList(Category1Type),
args: { name },
resolve: resolveCategory1
}}
})
And then the Category1Type is defined as:
const Category1Type = new GraphQLObjectType({
name: 'Category1',
description: '<>',
fields: () => ({
name: { type: GraphQLString },
category2: {
type: new GraphQLList(CategoryType2),
args: { secondName },
resolve: resolveCategory2
}
})
});
For simplicity sake, assume category2 is like so:
const Category2Type = new GraphQLObjectType({
name: 'Category2',
description: '<>',
fields: () => ({
name: { type: GraphQLString },
})
});
Now I want to fetch all Category2 items under Category1 with option to filter, like so:
Category1(name: $name){
name
category2(name: $name){
name
}}
My resolvers are defined like so:
# Category1 resolver
function cat1resolve (root, args) {
return SELECT * from data WHERE category1_name = args.name
}
# Category2 resolver
function cat2Resolve (root, args) {
return SELECT * from data WHERE category1_name = rootargs.name and categort2_name = args.secondName }
Now the problem is that the 'resolver' for cat2Resolve is not able to see or receive the rootargs.name for me to do this kind of filtering.

The resolve function signature includes 4 parameters. From Apollo's docs:
obj: The object that contains the result returned from the resolver on the parent field, or, in the case of a top-level Query field, the
rootValue passed from the server configuration. This argument enables
the nested nature of GraphQL queries.
args: An object with the arguments passed into the field in the query. For example, if the field was called with author(name: "Ada"),
the args object would be: { "name": "Ada" }.
context: This is an object shared by all resolvers in a particular query, and is used to contain per-request state, including
authentication information, dataloader instances, and anything else
that should be taken into account when resolving the query. If you’re
using Apollo Server, read about how to set the context in the setup
documentation.
info: This argument should only be used in advanced cases, but it contains information about the execution state of the query, including
the field name, path to the field from the root, and more. It’s only
documented in the GraphQL.js source code.
Note: These docs are for graphql-tools' makeExecutableSchema (which I highly recommend) but the same applies to plain old GraphQL.JS.
The key point here is that a resolver for a particular field is generally agnostic to what other resolvers do or what information is passed to them. It's handed its own parent field value, its own arguments, the context and expected to work with that.
However, there is a workaround utilizing the info parameter. The object passed to info is huge and can be complicated to parse, but contains virtually all the information about the requested query itself. There are libraries out to help with parsing it, but you may want to print the whole thing to console and poke around (it's pretty cool!).
Using something like lodash's get, we can then do:
const category1id = get(info, 'operation.selectionSet.selections[0].arguments[0].value.value')
and utilize that value inside your query. The above is pretty fragile, since it assumes your request only contains the one query, and you only have one argument on the Category1 field. In practice, you'd probably want to utilize Array.find and look up the fields/arguments by name, but this should give you a starting point.

Related

Derive locally cached Apollo client state based off query/cached object updates

I have a query that retrieves a Model. Inside this model, there are nested models with fields.
The shape is roughly like this:
{
model: [
{
id: 1,
fields: [...]
},
{
id: 2,
fields: [...]
}
]
}
Additionally, the frontend needs the model normalized into a list, like this:
{
modelFields: [
{...},
{...},
{...},
{...}
]
}
I’m attempting to derive modelFields declaratively when a query or cache update changes model. I’m trying to achieve this in type-policies section on Model: { merge: modelMergeMiddleware }, like so:
export function modelMergeMiddleware(
__: ModelFragment,
incoming: ModelFragment,
{cache, readField}: FieldFunctionOptions
) {
if (incoming) {
cache.writeQuery({
query: ModelFieldsDocument,
data: {
modelFields: incoming.fieldsets.reduce(
(fields: ModelFieldFragment[], fieldset: FieldsetFragment) => {
return fields.concat(newFields)
},
[]
)
}
})
}
return incoming
}
However, this runs into problems:
nested cache references don’t get passed through leaving empty data
readField and lodash’s _.cloneDeep both result in Readonly data that cause errors
My question is two-fold:
Is there a method to work around the problems mentioned above to derive data in a merge function?
Is there a different approach where I can declaratively derive local-only state and keep it synchronized with cached objects?
Per question 2, my backup approach is to use a reactiveVar/Recoil to store this data. This approach has the tradeoff of needing to call a setter function in all the places the Model object gets queried or mutated in the app. Not the end of the world, but it’s easy to miss a spot or forget about the setter function.

Filter react-admin query by dates

I am using react-admin's useGet... query to gather data from my rails backend. The main problem here is the filter property (the last pair of curly braces in the useGetList operation). How can i filter the data by Dates (like get only transactions of the last month etc.)
This is the current (working) approach:
const { data, loading, error } = useGetList(
'transactions',
{ page: 1, perPage: 10000 },
{ field: 'id', order: 'ASC' },
{},
)
if (loading) return <p>Loading</p>
if (error) return <p>Error</p>
if (!data) return null
The entries in the database all have a createdAt and updatedAt property.
My approach would be to create a filter like this:
// constraints could be dates that I can easily set beforehand
{
{'createdAt', desiredLowerTimeConstraint, operation: '<='},
{'createdAt', desiredUpperTimeConstraint, operation: '<='}
}
The react-admin documentation is quite sparce with the filter property, I couldn't find good examples for how these objects are supposed to look like.
It all depends on how your API expects filters to look like.
For instance, in REST APIs served by JSONServer, a _lte suffix on a query string parameter name indicates that you want results "Less Than or Equal to" the value:
GET /transactions?createdAt_lte=2019-12-05
Provided you use the ra-data-simple-rest, you can craft this request by passing the parameter in the filter:
const { data, loading, error } = useGetList(
'transactions',
{ page: 1, perPage: 10000 },
{ field: 'id', order: 'ASC' },
{ createdAt_lte: '2019-12-05' },
)
If your API behaves differently, then you may use the same syntax for useGetList, and transform the parameter in your dataProvider before it's sent to the API.

Tell apollo-client what gets returned from X query with Y argiments?

I have a list of Items of whatever type. I can query all of them with query items or one with query item(id).
I realize apollo can't know what will be returned. It knows the type, but it doesn't know the exact data. Maybe there is a way not to make additional request? Map one query onto another?
Pseudo-code:
// somewhere in Menu.tsx (renders first)
let items = useQuery(GET_ITEMS);
return items.map(item => <MenuItemRepresenation item={item} />);
// meanwhile in apollo cache (de-normalized for readability):
{ ROOT_QUERY: {
items: [ // query name per schema
{ id: 1, data: {...}, __typename: "Item" },
{ id: 2, data: {...}, __typename: "Item" },
{ id: 3, data: {...}, __typename: "Item" },
]
}
}
// somewhere in MainView.tsx (renders afterwards)
let neededId = getNeededId(); // 2
let item = useQuery(GET_ITEM, { variables: { id: neededId } } );
return <MainViewRepresentation item={item} />;
Code like this will do two fetches. Even though the data is already in the cache. But it seems apollo thinks on query level. I would like a way to explain to it: "If I make item query, you need to look over here at items query you did before. If it has no item with that id go ahead and make the request."
Something akin to this can be done by querying items in MainView.tsx and combing through the results. It might work for pseudo-code, but in a real app it's not that simple: cache might be empty in some cases. Or not sufficient to satisfy required fields. Which means we have to load all items when we need just one.
Upon further research Apollo Link looks promising. It might be possible to intercept outgoing queries. Will investigate tomorrow.
Never mind apollo link. What I was looking for is called cacheRedirects.
It's an option for ApolloClient or Cache constructor.
cacheRedirects: {
Query: {
node: (_, args, { getCacheKey }) => {
const cacheKey = getCacheKey({
__typename: "Item",
id: args.id,
});
return cacheKey;
},
},
},
I'd link to documentation but it's never stable. I've seen too many dead links from questions such as this.

How to use interface to type function argument in Flow

I'm trying to implement a React component that contains a list of options and shows their id and name. I want this component to be reusable so I define an interface Option to ensure the required fields are always provided.
And here comes the issue: if I pass any type with more fields than those 2 { id, name, /* anything */}, Flow complains. Is it not possible to use interfaces in Flow like this?
Here's the minimal relevant code:
interface Option {
id: string,
name: string
}
const List = (options: Option[]) => {
options.forEach(o => null)
}
type ImplementsOption = {
id: string,
name: string,
description: string
}
const plans: ImplementsOption[] = []
List(plans)
Error:
Cannot call List with plans bound to options because property description is missing in Option 1 but exists in ImplementsOption [2] in array element.
Trying with casting:
List((plans: Option[]))
And also with classes:
class ComplexOption implements Option {
id: string
name: string
}
const complexOptions: ComplexOption[] = []
List(complexOptions)
Nothing seems to work!
There is a playground with all these snippets already.
Imagine we had a list of ImplementsOption: [{ id: 'id', name: 'name', description: 'description' }, ...]. Now we pass it into the List function, which has the signature Option[] => void. This is totally valid from the point of view of List since ImplementOption is a supertype of Option. However, there is no guarantee in the List function that it won't modify the list that is passed in. Thus, the function could add an element of type Option to the list, which would be valid for a Option[] but invalid for a ImplementsOption[].
To fix this, you can type plans as a $ReadOnlyArray<Option>, which tells Flow that the List function will not modify the elements (try flow).
Reference issues #4425, #4483, or #5206 for more information.

mongoose query: find an object by id in an array

How could I find an image by id in this Schema. I have the id of the User and the id of the image I am looking for. What would be the best way to do this and do all images in this case have different ids or could they have the same id because they don't belong to the same User?
My Schema looks like this:
var userSchema = new Schema({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
name: String,
about: String,
images: [{
id: Schema.ObjectId,
link: String,
main: Boolean
}]
});
When you are interested in the full object it is a simple find:
.find({"facebook.id":"<id>", "images.id":<image-id>})
I don't think that there is a way to reduce the image array in the result.
To update a single element in the image array you can use this:
.update({"facebook.id":"<id>", "images.id":<image-id>}, {$set : {"images.$.main" :false} } );
userSchema .find({facebook.id: "some ID",{ "images.id": { $in: [ id1, id2, ...idn] }}
since images are inside the document you can have same ID's however every time you query you should keep in mind that you send some other parameters such as facebook.id or facebook.email along with image id's to retrieve them. Otherwise you end up getting all that might be irrelevant only because you decide to keep same ID's for images.
tl;dr
I struggled with this and came up with a solution. Like you, I was trying to query for a deeply nested object by the _id, but I kept coming up empty with the results. It wasn't until I did some type checking that I realized the id value I was getting from my frontend, while directly supplied by mongoose, was in fact a String and not an Object.
I realize this question was already partially answered before, but that person's solution didn't work for me, and the comment on the answer tells me you wanted to update the specific image you queried for, which is exactly what I was trying to do.
The solution
In order to select an object from the nested array by the _id value, first you'll have to install the npm package bson-objectid and use the provided method to convert your string into an objectId in your query.
In your terminal:
npm i bson-objectid
In your code:
const ObjectId = require('bson-objectid')
userSchema.findOneAndUpdate(
{ "facebook.id": <user-id>, "images._id": ObjectId(<image-id>) },
{ "$set": { "images.$.main": false } },
{ new: true }, // an extra options parameter that returns the mutated document
(err, user) => {
if (err) {
handleErr(err)
} else {
console.log(user)
// do something with new user info
}
)

Resources