denormalise reverse processStrategy - normalizr

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.

Related

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

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

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 update part of my amplify graphql model?

I am new with Aws Amplify and GraphQL and I am trying to find the best way to handle the following:
I want to be able to edit my FAQ in the frontend. The frontend gets the data from the backend, i edit one field in the frontend and save it, it should be stored in the backend
I have created a simple amplify graphql schema:
type FAQ #model(timestamps: null) {
id: ID!
sectionTitle: String
questionAndAnswer: [QuestionAndAnswer!]!
}
type QuestionAndAnswer {
id: ID!
question: String
answer: String
}
Then I populated it so it contains the following data:
{
"data": {
"listFAQS": {
"items": [
{
"sectionTitle": "Title2",
"id": "22735682-2bab-4695-b93e-1e50642d4654",
"questionAndAnswer": [
{
"answer": "answer4",
"id": "4",
"question": "question4"
},
{
"answer": "answer5",
"id": "5",
"question": "question5"
},
{
"answer": "answer6",
"id": "6",
"question": "question6"
}
]
},
{
"sectionTitle": "Title1",
"id": "21be8234-69f2-47fe-b942-d3e655197c70",
"questionAndAnswer": [
{
"answer": "answer1",
"id": "1",
"question": "question1"
},
{
"answer": "answer2",
"id": "2",
"question": "question2"
},
{
"answer": "answer3",
"id": "3",
"question": "question3"
}
]
}
]
}
}
}
Is there an easy way I can only change for instance the question "question4" without having to send the whole FAQ "row"?
When I was experimenting with it it seemed that the whole array was reseted and only the new data was saved. Should both FAQ and QuestionAndAnswer be a #model each where I connect them with #connection and update them separately as needed?
The following seems to be an easy way, as you can update only "question4" if you need, by using its own unique ID with GraphQL mutation.
type FAQ #model(timestamps: null) {
id: ID!
sectionTitle: String
questionAndAnswer: [QuestionAndAnswer!]!
}
type QuestionAndAnswer #model {
id: ID!
question: Question
answer: Answer
}
type Question #model {
id: ID!
question: String
}
type Answer #model {
id: ID!
answer: String
}
I did not include #connections (they should be in QuestionAndAnswer model), but 'Yes' would be the answer to " Should both FAQ and QuestionAndAnswer be a #model each where I connect them with #connection and update them separately as needed?".

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"
}
]
}
]

apollo-link-state add field with default value to type

Here's what I'm trying to accomplish:
I have a graphql API endpoint that returns me a Project object like this(unrelated fields removed):
{
"data": {
"Project": {
"id": "cjp4b84wkochq0167gpu8oa7h",
"requests": [
{
"id": "cjpbb6jcdpwj00167y4acl5a1",
"__typename": "Request"
},
{
"id": "cjpbbhlaxpwlx01675jzfyb0j",
"__typename": "Request"
},
{
"id": "cjpbbifg7pwmg0167s0ob1bm6",
"__typename": "Request"
},
],
"__typename": "Project"
}
}
}
I want to use apollo-link-state to add a client-side field to all of these Request objects like this:
{
"data": {
"Project": {
"id": "cjp4b84wkochq0167gpu8oa7h",
"requests": [
{
"id": "cjpbb6jcdpwj00167y4acl5a1",
"expanded": false,
"__typename": "Request"
},
{
"id": "cjpbbhlaxpwlx01675jzfyb0j",
"expanded": false,
"__typename": "Request"
},
{
"id": "cjpbbifg7pwmg0167s0ob1bm6",
"expanded": false,
"__typename": "Request"
},
],
"__typename": "Project"
}
}
}
This would allow me to remove local state from my Component that renders these requests. The problem is that when I define defaults for my ApolloClient clientState as follows:
const client = new ApolloClient({
clientState: {
defaults: {
Project: {
__typename: 'Project',
requests: [{ __typename: 'Request', expanded: false }],
},
},
},
});
Apollo adds it as a new Project object instead of adding it to the existing one(which has an id):
ROOT_QUERY
Project: Project
requests: [Request]
0:
expanded: false
Project({"id":"cjp4b84wkochq0167gpu8oa7h"}): Project
▾Project:cjp4b84wkochq0167gpu8oa7h
when I give it the id it adds the "hi" field to the correct project but the requests are still missing the expanded field. And giving the id only works for a specific project obviously.
const client = new ApolloClient({
clientState: {
defaults: {
'Project({"id":"cjp4b84wkochq0167gpu8oa7h"})': {
__typename: 'Project',
hi: true,
requests: [{ __typename: 'Request', expanded: false }],
},
},
},
});
ROOT_QUERY
Project({"id":"cjp4b84wkochq0167gpu8oa7h"}): Project
▾Project:cjp4b84wkochq0167gpu8oa7h
hi: true
requests: [Request]
0:▾Request:cjpbb6jcdpwj00167y4acl5a1
...unrelated fields
1:▾Request:cjpbbhlaxpwlx01675jzfyb0j
2:▾Request:cjpbbifg7pwmg0167s0ob1bm6
I also tried using the typeDefs field on the clientState object like this:
typeDefs: [`
schema {
query: RootQuery
}
type RootQuery {
Project($id: ID): Project
}
type Project {
id: ID!
requests: [Request]
}
type Request {
id: ID!
expanded: Boolean
}
`],
but this doesn't seem to change anything on the cache and I don't know if I can even give it a default value like this.
Maybe I'm misunderstanding how apollo-link-state works (or even how graphql works) any answer to point me in the right direction is appreciated. I'm very much a beginner when it comes to graphql or apollo.
You need to provide a client side resolver to your clientState configuration.
const clientState = {
resolvers: {
Project {
expanded: () => false
}
}
}
And then you'd pass this into your ApolloClient like so
const apolloClient = new ApolloClient({ clientState });

Resources