Apollo is not letting me edit an object field because it is readonly, but I cannot just make a copy of it - graphql

I am not sure how I should set cart.items to a new array, I have already made a copy of the original cache because I learned Apollo does not let you directly edit the cache, but I am still getting the following error
Error: Cannot assign to read only property 'items' of object '#'
Do I need to make a copy of the items array? And if so how do I go about changing the array on the current objects item field?
Here are my console.logs
You can ignore the typename fields as they are irrelevant to the problem
addItem
{
"__typename": "Cart",
"items": [
{
"__typename": "CartItem",
"name": "Item 3"
},
{
"__typename": "CartItem",
"name": "Item 4"
},
{
"__typename": "CartItem",
"name": "New Item!"
}
]
}
carts
{
"carts": [
{
"__typename": "Cart",
"id": "1",
"items": [
{
"__typename": "CartItem",
"id": "1",
"name": "Item 1"
},
{
"__typename": "CartItem",
"id": "2",
"name": "Item 2"
}
]
},
{
"__typename": "Cart",
"id": "2",
"items": [
{
"__typename": "CartItem",
"id": "3",
"name": "Item 3"
},
{
"__typename": "CartItem",
"id": "4",
"name": "Item 4"
}
]
}
]
}

So it seems you need to remake the items array as well since items is its own gql object type the easiest way to do this was to do it all at once with a map.
Please note the comment as that was an important detail I learned
// IMPORTANT NOTE when updating the cache of a query you must return the
// same fields as the original query even if you aren't using them in the code
const GET_CARTS = gql`
query {
carts{
id
items{
id
name
}}} `;
const MUTATION = gql`
mutation AddItem($input:MutationAddItemInput!) {
addItem(input: $input){
items{
id
name
}
}
}
`;
const { loading, error, data } = useQuery(GET_CARTS)
const [addItem] = useMutation(MUTATION, {
// refetchQueries: [{ query: GET_CARTS }]
update(cache, { data: { addItem } }) {
// addItem is the response of the query of add item function
console.log({ addItem });
// #ts-ignore
let { carts } = cache.readQuery({ query: GET_CARTS });
console.log({ carts })
// make a new array out of the carts array and add the new item to the array if the id of the cart is 2
let newCarts = carts.map((cart: Cart) => {
if (cart.id === "2") {
return { ...cart, items: [...addItem.items] }
} else {
return cart
}
})
console.log({ newCarts });
cache.writeQuery({
query: GET_CARTS,
data: { carts: newCarts }
// data: { carts: [{ id: "1", items: [{ id: "2", name: "an item" }] }] }
})
}
})
And lastly you will call the addItem function from the use mutation hook

Related

How to pass variables into a graphql query?

I am using the storefront API to create a headless e commerce site. I am having an issue passing variables into the query - where if I hard code the values it seems to work but as soon as I try and pass variables into the query it fails and says
Argument 'lineItems' on InputObject 'CheckoutCreateInput' has an invalid value ([[object, Object]]). Expected type '[CheckoutLineItemInput!]'
I am pretty sure the problem has to do with the passing of the variables becuase I have tried most things - and come to this conclusion.
Here is the query and action function
export const createCheckout = async (items: IProductVariant[]) => {
const query = `
mutation {
checkoutCreate(input:
lineItems: ${items}
}) {
checkout {
id
webUrl
lineItems(first: 5) {
edges {
node {
title
quantity
}
}
}
}
}
}
`;
export const action: ActionFunction = async ({request}) => {
// get the form data from the POST
const formData = await request.formData()
const id = Array.from(formData.getAll('id'))
const quantity = Array.from(formData.getAll('quantity'))
let items = id.map((item , idx) => {
const newObj: IProductVariant = {}
newObj["variantId"] = item as string
newObj["quantity"] = Number(quantity[idx]) as number
return newObj
})
const res = await createCheckout(items)
return {res}
}
I think i need to modify the query to take a variable ?
Hmm.. I'm not sure that's how you call the GraphQL end point.
https://shopify.dev/api/storefront/2022-01/mutations/checkoutCreate
import Shopify from '#shopify/shopify-api';
const client = new Shopify.Clients.Storefront('your-development-store.myshopify.com', storefrontAccessToken);
// define your mutation (note how you define the input type first, then call
// the mutation with the input variable
const query =
`mutation checkoutCreate($input: CheckoutCreateInput!) {
checkoutCreate(input: $input) {
checkout {
id
webUrl
lineItems(first: 5) {
edges {
node {
title
quantity
}
}
}
}
}
}`;
// define the input data (this is from the docs, pass what is needed here)
const input = {
{
"allowPartialAddresses": true,
"buyerIdentity": {
"countryCode": ""
},
"customAttributes": [
{
"key": "",
"value": ""
}
],
"email": "",
"lineItems": [
{
"customAttributes": {
"key": "",
"value": ""
},
"quantity": 1,
"variantId": ""
}
],
"note": "",
"presentmentCurrencyCode": "",
"shippingAddress": {
"address1": "",
"address2": "",
"city": "",
"company": "",
"country": "",
"firstName": "",
"lastName": "",
"phone": "",
"province": "",
"zip": ""
}
};
// this is where you call the API passing in the query and variables
const data = await client.query({
data: {
"query": query,
"variables": {
"input": input,
"queueToken": queueToken
},
},
});

How to mutate a list of objects in an array as an argument in GraphQL completely

I cannot mutate a list of objects completely, because only the last element of the array will be mutated.
What already works perfectly is, if I put each element ({play_positions_id: ...}) in the array manually like here:
mutation CreateProfile {
__typename
create_profiles_item(data: {status: "draft", play_positions: [{play_positions_id: {id: "1"}}, {play_positions_id: {id: "2"}}]}) {
id
status
play_positions {
play_positions_id {
abbreviation
name
}
}
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1337",
"status": "draft",
"play_positions": [
{
"play_positions_id": {
"id": "1",
"abbreviation": "RWB",
"name": "Right Wingback"
}
},
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Since you can add many of those elements, I defined a variable/argument like here
mutation CreateProfile2($cpppi: [create_profiles_play_positions_input]) {
__typename
create_profiles_item(data: {status: "draft", play_positions: $cpppi}) {
id
status
play_positions {
play_positions_id {
id
abbreviation
name
}
}
}
}
Variable object for above:
"cpppi": {
"play_positions_id": {
"id": "1"
},
"play_positions_id": {
"id": "2
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1338",
"play_positions": [
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Schema:
input create_profiles_input {
id: ID
status: String!
play_positions: [create_profiles_play_positions_input]
}
input create_profiles_play_positions_input {
id: ID
play_positions_id: create_play_positions_input
}
input create_play_positions_input {
id: ID
abbreviation: String
name: String
}
At the last both snippets, only the last object with the id "2" will be mutated. I need these to use the defined input type from my backend.
I figured it out. I got it wrong with the brackets in the variable. Here the solution:
"cpppi": [
{
"play_positions_id": {
"id": "1"
}
},
{
"play_positions_id": {
"id": "2"
}
}
]

Incrementing a value in nested attributes in AWS Lambda and DynamoDB

This is my query to add a new field or increment a nested attribute
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players.#player.#score = players.#player.#score + :s',
ExpressionAttributeNames: {
'#player': playerId,
'#score': 'score'
},
ExpressionAttributeValues: {
':s': 1
},
ReturnValues: "ALL_NEW"
};
This is the error I get
{
"message": "The document path provided in the update expression is invalid for update",
"code": "ValidationException",
"time": "2020-05-21T03:03:14.328Z",
"requestId": "Q04QEP1G3E2LAM43I04ADLM4IRVV4KQNSO5AEMVJF66Q9ASUAAJG",
"statusCode": 400,
"retryable": false,
"retryDelay": 27.814212380235393
}
My object looks like
{
"id": "09e7a690",
"players": {
"M3EDJeHtoAMCLJg": [
{
"cardId": "1",
"cardTitle": "test",
"pun": "this is a pun"
}
]
}
}

How to populate only with content of getter

I have some problem in mongoose project.
I try to populate and use getter but not all data
But now all virtuals appear in document.
I'm using mongoose.Schema and mongoose.Model
Here is example of my test code
const GroupsSchema = schema({
title: String,
users: [{
type: schema.Types.ObjectId,
ref: 'Users'
}]
});
const UsersSchema = schema({
name: String,
avatar: String
}, {
toJSON: {
virtuals: true
}
});
class Users extends Model {
get name() {
return {
name: this.name
};
}
get avatar() {
return {
avatar: this.avatar
};
}
}
Populating document
const groups = await Groups.find({}).populate('users').exec();
My current result:
[
{
"_id": "5c9bb51626924f0a08aa8c3d",
"title": "GroupName"
"users": [
{
"_id": "5c8e37169fc1f9000f8c333b",
"name": "Jack",
"avatar": "avatar.jpg",
"name": {
"name": "Jack",
},
"avatar": {
"avatar": "avatar.jpg"
}
}
]
}
]
How can I populate document with content of only name getter?
Desired result:
[
{
"_id": "5c9bb51626924f0a08aa8c3d",
"title": "GroupName"
"users": [
{
"name": "Jack"
}
]
}
]

denormalise reverse processStrategy

I have an API that gives out data like this with the attributes in a fields property.
{
records: [
{
id: "123",
fields: {
author: {
id: "1",
name: "Paul"
},
title: "My awesome blog post",
comments: [
{
id: "324",
commenter: {
id: "2",
name: "Nicole"
}
}
]
}
}
]
};
When normalizing, I now handle this with a simple processStrategy: (input, parent, key) => input.fields but I would like denormalise this again so that the denormalised entities to contain this fields structure because the API expects it this way.
So far denormalising my normalised data with const denormalizedData = denormalize([123], [article], normalizedData.entities) omits the field:
[
{
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
]
I cannot find anything in the api docs on how to add extra processing on denormalisation, any idea?
Because processStrategy is intended for pre-processing of data during the normalization process, it is not going to be executed during the denormalization. For your use case, I would not use this feature and simply structure your schemas as follows:
const { schema, denormalize, normalize } = normalizr;
const user = new schema.Entity("users");
const comment = new schema.Entity("comments", { commenter: user });
const commentList = [comment];
const post = new schema.Entity("posts", {
fields: { author: user, comments: commentList }
});
const postList = [post];
const mockApiResponse = {
records: [
{
id: "123",
fields: {
author: {
id: "1",
name: "Paul"
},
title: "My awesome blog post",
comments: [
{
id: "324",
commenter: {
id: "2",
name: "Nicole"
}
}
]
}
}
]
};
const normalizedResponse = normalize(mockApiResponse.records, postList);
const denormalizedResponse = denormalize(
normalizedResponse.result,
postList,
normalizedResponse.entities
);
console.log("normalizedResponse", normalizedResponse);
console.log("denormalizedResponse", denormalizedResponse);
This will give you the result you are looking for. If for some reason, you need to stick to your current implementation, I would recommend implementing a transform on your request prior to sending it back to the server. As an example, axios solves this with their transformRequest feature.

Resources