How to update nested structures in apollo cache using cache.modify - graphql

fragment commentFragment on Comment {
id
text
galleryId
commentUser {
id
firstName
lastName
}
}
fragment galleryFragment on Gallery {
id
path
label
comments {
...commentFragment
}
}
We first retrieve the getGalleries using the following gql :
query getGalleries($filters: galleryFilterInput) {
getGalleries(filters: $filters) {
galleries {
...galleryFragment
}
cursor
hasMore
}
}
Now when the user enters a comment on a single gallery item we run the following mutation :
mutation addCommentMutation($input: addCommentInput!) {
addComment(input: $input) {
...commentFragment
}
}
Now, we were previously using refetchQueries to update the Galleries but we have now decided to use cache.modify however we are having problem with updating the galleries
update: (cache, data: any) => {
cache.modify({
fields: {
getGalleries(existing, { readField }) {
const comment = data.data.addComment;
const newEventRef = cache.writeFragment({
fragment: commentFragment,
data: comment,
fragmentName: "commentFragment",
});
const index = existing.galleries.findIndex(
aGallery => aGallery.id === comment.galleryId
);
if (index !== -1) {
const existingCommentRef = readField("comments", existing.galleries[index])
as readonly Reference;
const newCommentsRefs = [...existingCommentRef, newRef];
cache.writeFragment({
id: "Gallery:" + readField("id", existing.galleries[index]),
fragment: gql`
fragment comments on Gallery {
comments {
...commentFragment
}
}
`,
data: newCommentsRefs,
});
}
return existing;
},
},
});
},
I am unsure how I update the newCommentsRefs in that Gallery

update(cache, data) {
const comment = data.data.addComment;
cache.writeFragment({
fragment: commentFragment,
data: comment,
fragmentName: "commentFragment",
});
const gallery: Gallery = cache.readFragment({
id: `Gallery:${comment.galleryId}`,
fragment: galleryFragment,
fragmentName: 'galleryFragment'
});
if (gallery) {
const newComments = [...gallery.comments, comment]
cache.writeFragment({
id: `Gallery:${comment.galleryId}`,
fragment: galleryFragment,
fragmentName: 'galleryFragment',
data: { ...gallery, comments: newComments }
});
}
}

Related

Dynamic routing using graphQL in a Next.js app

I'm building a webpage that consumes the spaceX graphQL api, using apollo as a client. On the landing page I want to display a 'launches' card, that when clicked on, directs to a new page with details about that particular launch, as below:
index.js
import { ApolloClient, InMemoryCache, gql } from "#apollo/client"
import Link from 'next/link'
export const getStaticProps = async () => {
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
})
const { data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
mission_patch
}
rocket {
rocket_name
}
}
}
`
});
return {
props: {
launches: data.launchesPast
}
}
}
export default function Home({ launches }) {
return (
<div>
{launches.map(launch => {
return(
<Link href = {`/items/${launch.id}`} key = {launch.id}>
<a>
<p>{launch.mission_name}</p>
</a>
</Link>
)
})}
</div>
)
}
I've set up a new page items/[id].js to display information about individual launches, but this is where the confusion is. Using a standard REST api I'd simply use fetch, then append the id to the end of the url to retrieve the desired data. However I'm not sure how to do the equivalent in graphQL, using the getStaticPaths function. Any suggestions?
Here's items/[id]/js, where I'm trying to render the individual launch data:
import { ApolloClient, InMemoryCache, gql } from "#apollo/client"
export const getStaticPaths = async () => {
const client = new ApolloClient({
uri: "https://api.spacex.land/graphql/",
cache: new InMemoryCache(),
});
const { data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
}
}
`,
});
const paths = data.map((launch) => {
return {
params: { id: launch.id.toString() },
};
});
return {
paths,
fallback:false
}
};
export const getStaticProps = async (context) => {
const id = context.params.id
// not sure what to do here
}
const Items = () => {
return (
<div>
this is items
</div>
);
}
export default Items;
for getStaticPaths
export const getStaticPaths = async () => {
const { data } = await client.query({
query: launchesPastQuery, // this will query the id only
});
return {
paths: data.CHANGE_THIS.map((param) => ({
params: { id: param.id },
})),
fallback: false,
};
};
CHANGE_THIS is the Query Type that follows data in the JSON response.
for getStaticProps
export const getStaticProps = async ({
params,
}) => {
const { data } = await client.query({
query: GetLaunchPastByID ,
variables: { LaunchID: params.id, idType: "UUID" }, // the idType is optional, and the LaunchID is what you'll use for querying by it*
});
return {
props: {
launchesPast: data.CHANGE_THIS,
},
};
The launchPastQueryByID is like:
const GetLaunchPastByID = gql`
query LaunchPastByID($LaunchID: UUID!) { // UUID is id type
CHANGE_THIS(id: $LaunchID) {
id
//...
}
}
`;
sorry for not giving you the correct queries, spacex.land is currently down.

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");
}

Variable "$productSlug" is never used in operation "SingleProduct". Graphql error. Variables are not working in my Gatsby graphql queries

I am getting this error: Variable "$productSlug" is never used in operation "SingleProduct".
I also use gatsby-source-wordpress to query fields from wordpress to gatsby. I also uninstalled Gatsby and re-installed it to see if it works in different versions, but it didn't.
I searched all over the internet and stack overflow for an answer, I really believe it must be a bug with Gatsby or gatsby-source-wordpress,
this is the code:
const path = require("path");
const { createFilePath } = require(`gatsby-source-filesystem`);
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions;
if (page.path.match(/products/)) {
page.context.layout = "ArchiveProduct";
createPage(page);
}
if (page.path.match(/products\/([^\/]+$)/)) {
page.context.layout = "SingleProduct";
createPage(page);
}
};
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `allWpProduct`) {
const slug = createFilePath({ node, getNode, basePath: `pages` });
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
exports.createPages = async function ({ graphql, actions }) {
const { data } = await graphql(`
query SingleProduct {
allWpProduct {
nodes {
uri
slug
id
}
}
}
`);
data.allWpProduct.nodes.forEach((node) => {
// const slug = node.slug;
actions.createPage({
path: "/products/" + node.slug,
component: path.resolve("./src/templates/SingleProduct.js"),
context: {
productSlug: node.slug,
productId: node.id,
layout: "SingleProduct",
},
});
});
};
And this is the query:
export const query = graphql`
query SingleProduct($productSlug: String!) {
wpProduct(slug: { eq: "$productSlug" }) {
title
slug
link
id
date(formatString: "MMMM DD, YYYY")
product {
description
price
slug
photo {
localFile {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
`;
Try the following:
export const query = graphql`
query SingleProduct($productSlug: String!) {
wpProduct(filter: {slug: { eq: "$productSlug" }}) {
title
slug
link
id
date(formatString: "MMMM DD, YYYY")
product {
description
price
slug
photo {
localFile {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
`;
Your issue appears because $productSlug is lifted properly via context but never used in any sort of filtering action inside the query.
I'd recommend you check it before in the GraphiQL playground hardcoding the $productSlug to check the output.

How do I add recursive logic in resolvers using GraphQL mutations?

Is it possible to add logic in resolvers using GraphQL mutations?
I am trying to create a four-digit string as an alias for a post if the user does not provide it. Then, I would like to check the database to see if the four-digit string exists. If the string exists, I would like to create another four-digit string recursively.
At the moment, I'm exploring adding logic to mutations within resolvers, but I'm not sure if this is doable. I'm using these documents for my foundation: graphql.org sequelize.org
This is my current code block:
Working as of 12/4/2020
const MakeSlug = require("./services/MakeSlug");
const resolvers = {
Query: {
async allLinks(root, args, { models }) {
return models.Link.findAll();
},
async link(root, { id }, { models }) {
return models.Link.findByPk(id);
}
},
Mutation: {
async createLink(root, { slug, description, link }, { models }) {
if (slug !== undefined) {
const foundSlug = await models.Link.findOne({
where: { slug: slug }
});
if (foundSlug === undefined) {
return await models.Link.create({
slug,
description,
link,
shortLink: `https://shink.com/${slug}`
});
} else {
throw new Error(slug + " exists. Try a new short description.");
}
}
if (slug === undefined) {
const MAX_ATTEMPTS = 10;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
attempts++;
let madeSlug = MakeSlug(4);
const foundSlug = await models.Link.findOne({
where: { slug: madeSlug }
});
if (foundSlug !== undefined) {
return await models.Link.create({
slug: madeSlug,
description,
link,
shortLink: `https://shink.com/${madeSlug}`
});
}
}
throw new Error("Unable to generate unique alias.");
}
}
}
};
module.exports = resolvers;
This is my full codebase.
Thank you!
A while loop solved the challenge. Thanks xadm.
const MakeSlug = require("./services/MakeSlug");
const resolvers = {
Query: {
async allLinks(root, args, { models }) {
return models.Link.findAll();
},
async link(root, { id }, { models }) {
return models.Link.findByPk(id);
}
},
Mutation: {
async createLink(root, { slug, description, link }, { models }) {
if (slug !== undefined) {
const foundSlug = await models.Link.findOne({
where: { slug: slug }
});
if (foundSlug === undefined) {
return await models.Link.create({
slug,
description,
link,
shortLink: `https://shink.com/${slug}`
});
} else {
throw new Error(slug + " exists. Try a new short description.");
}
}
if (slug === undefined) {
const MAX_ATTEMPTS = 10;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
attempts++;
let madeSlug = MakeSlug(4);
const foundSlug = await models.Link.findOne({
where: { slug: madeSlug }
});
if (foundSlug !== undefined) {
return await models.Link.create({
slug: madeSlug,
description,
link,
shortLink: `https://shink.com/${madeSlug}`
});
}
}
throw new Error("Unable to generate unique alias.");
}
}
}
};
module.exports = resolvers;

Apollo GraphQL: Calling a Query Twice with apolloClient.query?

I have the following query:
const GET_MY_USERINFOFORIMS_QUERY = gql`
query($userID: String!){
myUserDataForIMs(userID:userID){
name_first
name_last
picture_medium
}
} `;
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
}
,
props({ data: { loading, myUserDataForIMs } }) {
return { loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
From the Apollo docs, it looks like I may be able to call this query twice from inside the component, using apolloClient.query, doing something like this:
client.query({ query: query1 })
client.query({ query: query2 })
Is there a way to call the query twice, passing a different userID each time?
Found it. :)
const localThis = this;
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: fromID},
}).then((result) => {
localThis.setState({ fromAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: toID},
}).then((result) => {
localThis.setState({ toAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
If there's a better/more efficient way, please post it.
You can do this by passing Apollo's refetch() method into your component's props alongside the data:
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
},
props({ data: { refetch, loading, myUserDataForIMs } }) {
return { refetch, loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
...then somewhere in your component, you can refetch the data "manually":
theUserWasChangedSomehow(userID) {
this.props.refetch({ userID });
}

Resources