apollo client cache for nested queries - graphql

I have a nested query (query inside query) with apollo client.
Everything works great, I do the request and get the correct data, but the issue is when I'm trying to use the cache, the cache returns undefined for the nested query prop.
My query:
query GetStudents($first: Int!, $after: String) {
me {
id
email
firstName
lastName
students(first: $first, after: $after) {
edges {
node {
id
created
number
status
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
When I try to use the inMemoryCache, the students is always undefined :
new InMemoryCache({
typePolicies: {
Query: {
fields: {
me: {
keyArgs: false,
merge(existing = {}, incoming = {}, { readField }) {
const id = readField("id", incoming);
const email = readField("email", incoming);
const students = readField("students", incoming);
return {
...
};
},
},
}
}
}
});
I can read correctly the id and email from the cache, but the students (which is the nested query) will be always undefined.
Do I need to read the cache students in a different way because it is a query?

Related

Strapi custom service overwrite find method

I'm using strapi v4 and I want to populate all nested fields by default when I retrieve a list of my objects (contact-infos). Therefore I have overwritten the contact-info service with following code:
export default factories.createCoreService('api::contact-info.contact-info', ({ strapi }): {} => ({
async find(...args) {
let { results, pagination } = await super.find(...args)
results = await strapi.entityService.findMany('api::contact-info.contact-info', {
fields: ['locale'],
populate: {
sections: {
populate: { link: true }
}
}
})
return { results, pagination }
},
}));
That works well, but I execute a find all entries on the database twice, I guess, which I want to avoid, but when I try to return the result from the entityService directly I'm getting following response:
data": null,
"error": {
"status": 404,
"name": "NotFoundError",
"message": "Not Found",
"details": {}
}
also, I have no idea how I would retrieve the pagination information if I don't call super.find(). Is there any way to find all contents with the option to populate nested objects?
the recommended way of doing this, would be a middleware (do it once apply for all controllers). There would be an video Best Practice Session 003 where it's describes exactly this scenario (Not sure if it's discord only, but on moment of writing this it wasn't yet published).
So regarding rest of your question:
async find(...args) {
let { results, pagination } = await super.find({...args, populate: {section: ['link']})
}
should be sufficient to fix that up in one query
custom pagination example:
async findOne(ctx) {
const { user, auth } = ctx.state;
const { id } = ctx.params;
const limit = ctx.query?.limit ?? 20;
const offset = ctx.query?.offset ?? 0;
const logs = await strapi.db.query("api::tasks-log.tasks-log").findMany({
where: { task: id },
limit,
offset,
orderBy: { updatedAt: "DESC" },
});
const total = await strapi.db
.query("api::tasks-log.tasks-log")
.count({ where: { task: id } });
return { data: logs, meta: { total, offset, limit } };
}
one small addition to the accepted answer, the answer didn't work completely since args is an array with an object inside, so I had to do it like this:
async find(...args) {
const argsObj = args[0]
let { results, pagination } = await super.find({...argsObj, populate: {section: ['link']})
}

Apollo cache update is not reflected on paginated query

I have two components, one of the components creates new items, the other one displays them using an "infinite scroll list". These two components do not have a parent/child relationship and they're not rendered at the same time (they're on different "pages").
I've followed these docs and included the modified object in the mutation of my first component. And I can see the new object in the Apollo cache using dev tools. (Car:<some UUID> gets added in the cache after the mutation runs)
My paginated component is configured with relay style pagination, and the pagination works fine, but when I add a new item it doesn't appear in the list until I refresh the page.
My InMemoryCache looks like this:
...
typePolicies: {
// paginated results
Query: {
fields: {
cars: relayStylePagination()
}
},
CarsResult: {
fields: {
edges: {
// Concatenate the incoming list items with
// the existing list items.
merge(existing = [], incoming) {
return [...existing, ...incoming]
}
}
}
},
PageInfo: {
fields: {
endCursor: {
merge(existing, incoming) {
return incoming
}
}
}
}
}
The mutation looks like this:
${CAR_SUMMARY_FRAGMENT}
mutation CreateCar($name: String!) {
createCar(
input: {
name: $name
}
) {
...CarSummary
}
}
The CreateCar return type is Car
Then my paginated query:
query CarsPaginated($after: Cursor) {
cars(
page: { first: 25, after: $after }
orderBy: { field: CREATE_TIME, direction: DESC }
) {
edges {
node {
...CarSummary
}
}
totalCount
pageInfo {
hasNextPage
endCursor
}
}
}
The CarsPaginated return type is CarsResult:
type CarsResult {
edges: [CarEdge]
pageInfo: PageInfo!
totalCount: Int!
}
type CarEdge {
node: Car
cursor: Cursor!
}
Ideally, I'd like the new item to show up at the top of my items list on the other component.
I've tried to use the "refetchQueries" attribute but the paginated query is not active since the list component is not rendered at that time.
Maybe there's something I need to do in the typePolicies?

WpGraphQL query returns null

I'm having this GraphQL query from headless Wordpress in Nexjs via WpGraphQl plugin:
export const GET_POSTS_BY_CATEGORY_SLUG = gql`
query GET_POSTS_BY_CATEGORY_SLUG( $slug: String, $uri: String, $perPage: Int, $offset: Int ) {
${HeaderFooter}
page: pageBy(uri: $uri) {
id
title
content
slug
uri
seo {
...SeoFragment
}
}
categories(where: {slug: $slug}) {
edges {
node {
slug
posts: posts(where: { offsetPagination: { size: $perPage, offset: $offset }}) {
edges {
node {
id
title
excerpt
slug
featuredImage {
node {
...ImageFragment
}
}
}
}
pageInfo {
offsetPagination {
total
}
}
}
}
}
}
}
${MenuFragment}
${ImageFragment}
${SeoFragment}
`;
And this is my getStaticProps function:
export async function getStaticProps(context) {
const { data: category_IDD } = await client.query({
query: GET_POSTS_BY_CATEGORY_SLUG,
});
const defaultProps = {
props: {
cat_test: JSON.parse(JSON.stringify([category_IDD])),
},
revalidate: 1,
};
return handleRedirectsAndReturnData(defaultProps, data, errors, "posts");
}
If i pass it like this in props:
const defaultProps = {
props: {
cat_test: category_IDD,
},
i get an error saying:
SerializableError: Error serializing `.cat_test` returned from `getStaticProps` in "/category/[slug]". Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
But when i JSON.parse as the code above, i get null
Whats wrong with this query?
Just noticed that the $slug is an array of strings, so here should be:
query GET_POSTS_BY_CATEGORY_SLUG( $slug: [String], $uri: String, $perPage: Int, $offset: Int )
instead of $slug: String
You're not actually passing the $slug variable to the query.
For instance if your page route is /category/[slug].js your getStaticProps should look something like this.
export async function getStaticProps(context) {
const { slug } = context.params;
const { data: category_IDD } = await client.query({
query: GET_POSTS_BY_CATEGORY_SLUG,
variables: { slug },
});
const defaultProps = {
props: {
cat_test: JSON.parse(JSON.stringify([category_IDD])),
},
revalidate: 1,
};
return handleRedirectsAndReturnData(defaultProps, data, errors, "posts");
}

GraphQL error: Variable $customerAccessToken of type String! was provided invalid value

I'm attempting to recover a customer using the customerAccessToken that is given when the user logs in to Shopify.
Using Apollo, here's my code:
this.apollo.mutate({
mutation: getCustomerFromToken,
variables: {
input: {
customerAccessToken: '217b9a6952c28eb4db376487a6301294' // Also tried btoa('217b9a6952c28eb4db376487a6301294')
}
},
})
Here's my GraphQL query:
query getCustomerFromToken($customerAccessToken: String!) {
customer(customerAccessToken: $customerAccessToken) {
id
addresses(first: 5) {
edges {
node {
address1
address2
company
city
province
zip
country
phone
}
}
}
orders(first: 200) {
edges {
cursor
node {
id
totalPriceV2 {
amount
currencyCode
}
processedAt
orderNumber
}
}
}
}
}
Here's the login GraphQL code I'm using to retrieve the accessToken from Shopify:
mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
My problem was two fold.
I was using a mutation on a query end point
Queries don't use input in the payload
const payload = {
customerAccessToken: "..."
}
// NOT
const payload = {
input: {
customerAccessToken: "..."
}
}

How do you filter a list response using a graphql query in Sangria

I am running a graphQL server on Sangria (scala). Here is an example query:
query {
missions {
missionId { id } ,
name
}
}
and a sample response:
{
"data": {
"missions": [
{
"missionId": {
"id": "mission1"
},
"name": "foo"
},
{
"missionId": {
"id": "mission2"
},
"name": "bar"
}
]
}
}
I am looking for a query that filters the list and only returns the element having mission1 as id?
You need implement pagination. Pass limit(pageSize) argument to graphql server resolver. process the datas in server-side.
query {
missions(limit: 1) {
missionId { id } ,
name
}
}
server-side:
const resolvers = {
Query: {
missions: (_, {limit}, ctx) => {
const missions = [];
for(let i = 0; i < limit; i++) {
missions.push(db.missions[i])
}
return missions;
}
}
}
That's graphql ideology, front-end developer define the data structure and what data they want to get.
It's bad idea to query the list data through a http request. And filter the data in front-end using directive or other way of graphql. Waste bandwidth.

Resources