Get All Pages with an Apollo Query - graphql

Suppose I have 500 rows in my database. I want to get 100 pages of 50 rows. Here is an example of a fetchMore request.
const fetchNextPage = (props) => {
props.Query.fetchMore({
query: gql(getRows),
variables: {
skip: props.Query.rows.length,
},
updateQuery: (previousResult, next) => {
return {
...previousResult,
rows: [...previousResult.rows, ...next.fetchMoreResult.rows],
};
},
});
}
What I'm unsure about is...
How I can fetch all pages without additional user action?
Since I know the total number of needed pages how can I send them in parallel?

You can actually do that with one operation. Using aliases you can request the same field multiple times with different arguments.
Here is the official explanation about aliases.
In your case it would be something similar to:
query GetAllPages {
page1rows: rows(skip: 0, limit: 50) { # "skip" and "limit" are just regular variable names
#...rowFields
}
page2rows: rows(skip: 50, limit: 50) {
#...rowFields
}
#... etc.
}
In this example page1rows and page2rows are aliases for the rows field. You can choose other aliases.
Note that skip and limit are nothing special, they are plain variables and their use depends on the schema and resolvers on the server. I see in your code you use skip, if you know you'll always get 50 rows then limit is redundant.
The response should be something like:
{
"data": {
"page1rows": [
//... rows
],
"page2rows": [
//... rows
],
//... etc.
}
}
That way works without additional user action, you get all pages at once and there's no need to use fetchMore.

Related

What is the recommended schema for paginated GraphQL results

Let's say I have users list to be returned. What would be best schema strategy among following.
Users returned contains only the data of user as follows, separate query is used for pagination details. In this query the downside is we need to pass same filters to both users and usersCount query.
query {
users(skip: 0, limit: 100, filters: someFilter) {
name
},
usersCount(filters: someFilters)
}
Which return following
{
results: {
users: [
{ name: "Foo" },
{ name: "Bar" },
],
usersCount: 1000,
}
}
In this strategy we make pagination details as part of users query, we don't need to pass filters twice. I feel this query is not nice to read.
query {
users(skip: 0, limit: 100, filters: someFilter) {
items: {
name
},
count
}
}
Which returns the following result
{
results: {
users: {
items: [
{ name: "Foo" },
{ name: "Bar" },
],
count: 1000,
}
}
}
I am curious to know which strategy is the recommended way while designing paginated results?
I would recommend to follow the official recommendation on graphql spec,
You need to switch to cursor based pagination.
This type of pagination uses a record or a pointer to a record in the dataset to paginate results. The cursor will refer to a record in the database.
You can follow the example in the link.
GraphQL Cursor Connections Specification
Also checkout how GitHub does it here: https://docs.github.com/en/graphql/reference/interfaces#node

Multiple queries in prisma graphql resolver

Following this example here:
https://github.com/prisma/prisma-examples/blob/latest/javascript/graphql-sdl-first/src/schema.js
Let's say I have a mutation where I want to update multiple users by passing their ids and emails accordingly. I know the updateMany would probably be the most suitable option, but since different users would have different values, not sure how to pass that without calling multiple resolvers separately. Something like this:
updateUsers: (_parent, args, context) => {
return context.prisma.user.updateMany({
where: {
id: { in: args.userIds },
},
data: {
email: ??? <--- use args.emails here
}
})
}
or should I just run multiple mutations:
updateUsers: async (_parent, args, context) => {
try {
args.emails.forEach(email => {
const user = await context.prisma.user.update({
where: {
id: { in: args.userIds },
},
data: {
email: args.email
}
})
return user;
}
} catch (error) {
console.log(error)
}
}
not sure if the last example would even work because of multiple return statements, since all the resolvers have a return statement, how can I run multiple queries/mutations?
updateMany allows you to bulk update all rows matching certain conditions with the same data. It does not help you in this case.
You could loop over an array of users to update id and email of each of them. Your approach will not work however, as you seem to have two arrays (a list of user IDs and a list of email addresses). However, instead of fixing your code I'd suggest to change the GraphQL interface you've defined.
Option 1) is close to what you did. You keep a bulk mutation updateUsers, but instead of two lists (a list of user IDs and a list of email addresses), it should accept a list of users, each of them having an user ID and an email address.
To improve performance you could use Promise.all and not wait for each update to happen, before starting the next one.
Option 2) is the preferable. Instead of a bulk mutation updateUsers, I would create a mutation updateUser that updates only one user. If client wants to update multiple users in the same request, they can! A single GraphQL request can contain multiple mutations.

Apollo mixes two different arrays of the same query seemingly at random

With a schema like
schema {
query: QueryRoot
}
scalar MyBigUint
type Order {
id: Int!
data: OrderCommons!
kind: OrderType!
}
type OrderBook {
bids(limit: Int): [Order!]!
asks(limit: Int): [Order!]!
}
type OrderCommons {
quantity: Int!
price: MyBigUint! // where it doesn't matter whether it's MyBigUint or a simple Int - the issue occurs anyways
}
enum OrderType {
BUY
SELL
}
type QueryRoot {
orderbook: OrderBook!
}
And a query query { orderbook { bids { data { price } }, asks { data { price } } } }
In a graphql playground of my graphql API (and on the network level of my Apollo app too) I receive a result like
{
"data": {
"orderbook": {
"bids": [
{
"data": {
"price": "127"
}
},
{
"data": {
"price": "74"
}
},
...
],
"asks": [
{
"data": {
"price": "181"
}
},
{
"data": {
"price": "187"
}
},
...
]
}
}
}
where, for the purpose of this question, the bids are ordered in descending order by price like ["127", "74", "73", "72"], etc, and asks are ordered in ascending order, accordingly.
However, in Apollo, after a query is done, I notice that one of the arrays gets seemingly random data.
For the purpose of the question, useQuery react hook is used, but the same happens when I query imperatively from a freshly initialized ApolloClient.
const { data, subscribeToMore, ...rest } = useQuery<OrderbookResponse>(GET_ORDERBOOK_QUERY);
console.log(data?.orderbook?.bids?.map(r => r.data.price));
console.log(data?.orderbook?.asks?.map(r => r.data.price));
Here, corrupted data of Bids gets printed i.e. ['304', '306', '298', '309', '277', '153', '117', '108', '87', '76'] (notice the order being wrong, at the least), whereas Asks data looks just fine. Inspecting the network, I find that Bids are not only properly ordered there, but also have different (correct, from DB) values!
Therefore, it seems something's getting corrupted on the way while Apollo delivers the data.
What could be the issue here I wonder, and where to start debugging such kind of an issue? There seem to be no warnings from Apollo either, it seems to just silently corrupt the data.
I'm clearly doing something wrong, but what?
The issue seems to stem from how Apollo caches data.
My Bids and Asks could have the same numeric IDs but share the same Order graphql type. Apollo rightfully assumes a Bid and an Ask with the same ID are the same things and the resulting data gets wrecked as a consequence.
An easy fix is to show Apollo that there's a complex key to the Order type on cache initialization:
cache: new InMemoryCache({
typePolicies: {
Order: {
keyFields: ['id', 'kind'],
}
}
})
This way it'll understand that the Order entities Ask and Bid with the same ID are different pieces of data indeed.
Note that the field kind should be also added to the query strings accordingly.

Passing Variables into GraphQL Query in Gatsby

I want to limit the number of posts fetched on my index page. Currently, the number of pages is hard-coded into the GraphQL query.
query {
allMarkdownRemark(limit: 5, sort: { fields: [frontmatter___date], order: DESC }) {
totalCount
edges {
node {
...
}
}
}
}
I want to replace "5" with the value of a variable. String interpolation will not work with the graphql function, so I have to use another method.
Is there a way to achieve this and pass variables into a GraphQL query in GatsbyJS?
You can only pass variables to GraphQL query via context since string interpolation doesn't work in that way. In page query (rather than static queries) you can pass a variable using the context object as an argument of createPage API. So, you'll need to add this page creation to your gatsby-node.js and use something like:
const limit = 10;
page.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/pages/index.js`), // your index path
// values in the context object are passed in as variables to page queries
context: {
limit: limit,
},
})
})
Now, you have in your context object a limit value with all the required logic behind (now it's a simple number but you can add there some calculations). In your index.js:
query yourQuery($limit: String) {
allMarkdownRemark(limit: $limit, sort: { fields: [frontmatter___date], order: DESC }) {
totalCount
edges {
node {
...
}
}
}
}

Updating Apollo Cache Without Variables

Originally, I make a GraphQL call as follows:
query getItems($filter_ids: [Int!], $filter: item_records_bool_exp) {
items(order_by: { negative: asc, parent: asc }, where: { level: { _in: [2, 3] } }) {
i18n {
value
}
id
parent
negative
}
filters(where: { category: { _eq: "PLAN" } }) {
id
value
}
}
Now, when I insert a new item, I update the cache using update function in the mutation options, and I'm supposed to use readQuery/readFragment and writeQuery/writeFragment to interact with the Apollo Cache as described here.
My question is, my readQuery calls always fail if I do not provide the exact same variables that I had previous provided to the original GraphQL query. Is there a way around this? In other words, can I just read objects from the cache by their ID irrespective of the original query that was used to fetch these objects?

Resources