Add an array of Objects to a mutation in apollo-react - reactjs

I am using react-apollo on the front-end and graphcool on the backend. I have a mutation that creates a tutorial like so:
const CREATE_TUTORIAL_MUTATION = gql`
mutation CreateTutorialMutation(
$author: String
$link: String
$title: String!
$postedById: ID!
$completed: Boolean!
) {
createTutorial(
author: $author
link: $link
title: $title
postedById: $postedById
completed: $completed
) {
author
link
title
postedBy {
id
name
}
completed
}
}
`
It gets called in a submit handler like so...
this.props.createTutorialMutation({
variables: {
author,
link,
title,
completed: false,
postedById
}
})
Everything works wonderfully.
Now I want to add a set of tags to when I create a new tutorial. I created the input field and connected it so that the tags variable is an array of objects, each with a tag id and the tag text.
If I try and add the tags field to the mutation it needs a scalar type. But there is doesn't seem to be a scalar type for an array of objects.
If I pass the tag variable in as a parameter when I call the mutation how do I fill in the Scalar type field in the mutation ( on line 148 here https://github.com/joshpitzalis/path/blob/graphQL/src/components/Add.js) and in the schema?
I am new to graphQL and I understand that I might be approaching this completely the wrong way. If that is the case, how do I add an array of objects to a mutation in graphQL?

You should add a new Tag type to your schema file and connect it to Tutorial with a new relation:
type Tutorial {
author: String
completed: Boolean
link: String
title: String!
id: ID! #isUnique
createdAt: DateTime!
updatedAt: DateTime!
postedBy: User #relation(name: "UsersTutorials")
tags: [Tag!]! #relation(name: "TutorialTags")
}
type Tag {
id: ID!
tag: String!
number: Int!
tutorials: [Tutorial!]! #relation(name: "TutorialTags")
}
Then you can create a new tutorial and new tags using a nested create mutation like this:
const CREATE_TUTORIAL_MUTATION = gql`
mutation CreateTutorialMutation(
$author: String
$link: String
$title: String!
$tags: [TutorialtagsTag!]!
$completed: Boolean!
$postedById: ID!
) {
createTutorial(
author: $author
link: $link
title: $title
tags: $tags
completed: $completed
postedById: $postedById
) {
author
link
title
postedBy {
id
name
}
completed
tags {
id
text
}
}
}
`
This post gives more background about other approaches and their trade-offs: https://www.graph.cool/forum/t/how-do-i-add-an-array-of-objects-to-a-mutation-in-apollo-react/365/6?u=nilan

What i understand by your requirement is that if you have the following code
const user = {
name:"Rohit",
age:27,
marks: [10,15],
subjects:[
{name:"maths"},
{name:"science"}
]
};
const query = `mutation {
createUser(user:${user}) {
name
}
}`
you must be getting something like
"mutation {
createUser(user:[object Object]) {
name
}
}"
instead of the expected
"mutation {
createUser(user:{
name: "Rohit" ,
age: 27 ,
marks: [10 ,15 ] ,
subjects: [
{name: "maths" } ,
{name: "science" }
]
}) {
name
}
}"
If this is what you wanted to achieve, then gqlast is a nice tag function which you can use to get the expected result
Simply grab the js file from here and use it as:
const user = {
name:"Rohit",
age:27,
marks: [10,15],
subjects:[
{name:"maths"},
{name:"science"}
]
};
const query = gqlast`mutation {
createUser(user:${user}) {
name
}
}`
The result stored in the variable query will be :
"mutation {
createUser(user:{
name: "Rohit" ,
age: 27 ,
marks: [10 ,15 ] ,
subjects: [
{name: "maths" } ,
{name: "science" }
]
}) {
name
}
}"

Related

Appsync Update and Delete mutation not working

I am using AppSync for my APP. Here is a quick look of my Schema.graphql file
type Item #model
#auth(rules: [
{ allow: public, provider: apiKey, operations: [read] },
{ allow: groups, groups: ["admin"] }
]) {
id: ID!
name: String!
soldUnits: String!
soldAs: [Float!]
price: Float!
isAvailable: Boolean!
availableAmount: Float!
category: String!
isVeg: Boolean!
offer: [String!]
about: String
storage: String
benifits: String
otherInfo: String
rating: Int
keywords: [String!]
reviews: [String]
images: [String]
updatedBy: String
addedBy: String
qty: Int
inventroy: Inventory #connection(name: "ItemInventory")
}
type User #model {
id: ID!
firstName: String!
lastName: String!
about: String
phoneNumber: String
email: String
countryCode: String
addresses: [Address] #connection
orders: [Order] #connection(name: "OwnOrder")
}
type CartItem {
id: ID!
count: Int!
name: String!
price: Float
}
type Address #model {
id: ID!
lat: Float!
long: Float!
fullAddress: String!
apartment: String!
street: String
area: String!
district: String!
city: String
state: String!
zip: Int!
country: String!
phoneNumber: String!
}
type InventoryAddress #model {
id: ID!
address: Address #connection
inventroy: Inventory! #connection(name: "InventoryAddress")
}
type Order #model {
id: ID!
orderdate: AWSDate!
ordertime: AWSTime!
deliverySlot: String!
deliveryDate: AWSDate!
deliveryInput: String!
deliveryPhoneNumber: String!
deliveryPhoneCountryCode: String!
deliveryAddress: String!
deliveredTime: AWSDate
deliveredDate: AWSDate
deliveryCharges: Float!
totalAmount: Float!
paidAmount: Float!
discount: Float!
unpaidAmount: Float!
status: String!
paymentMethod: String!
paymentId: String!
itemsCount: Int!
items: [CartItem]
deliverAssignedTo: Patron #connection(name: "PatronOrder")
owner: User #connection(name: "OwnOrder")
inventroyDetails: Inventory #connection(name: "InventoryOrder")
}
type Patron #model {
id: ID!
age: Int!
firstName: String!
lastName: String!
about: String
workTime: String
rating: Int!
reviews: [String]
addressProof: String!
role: String
workArea: Address #connection
address: Address #connection
deliveries: [Order] #connection(name: "PatronOrder")
inventroyDetails: Inventory! #connection(name: "PatronInventory")
addedItems: [Item] #connection
}
type Inventory #model {
id: ID!
address: InventoryAddress #connection(name: "InventoryAddress")
patron: [Patron] #connection(name: "PatronInventory")
items: [Item] #connection(name: "ItemInventory")
orders: [Order] #connection(name: "InventoryOrder")
manager: Patron #connection
admins: [Patron] #connection
}
This is my GraphQL Schema generated in AppSync.
I am able to query the data and do create mutation, but update and delete mutation is not working.
mutation UpdateAddressInput {
updateAddress(input:{
id:"af5cd7e6-8213-48b6-aa8e-9d2d6999742c",
area:"New Delhi"
}){
id,
area
}
}
This is an example query , as you can see the return data below, it's not reflecting in dynamoDB
{
"data": {
"updateAddress": {
"id": "af5cd7e6-8213-48b6-aa8e-9d2d6999742c",
"area": "Electronic City"
}
}
}
Even working with AWS Amplify, I prefer to go to the AppSync console and select the queries, and mutations this way you'll see what's happening and what's needed behind the scenes to do your mutations.
An update mutation sample bellow, extracted from the AppSync console - instead of using codegen from Amplify.
input UpdateRuralAddressInput {
id: ID!
latitude: String
longitude: String
stateCity: String
status: Int
_version: Int
}
Note that field version is required to do the update.
I'm just pointing it out to help others with this issue, that's how I'm working with Amplify + Appsync client in Lambdas
So, before applying any update mutation execute a "get by id" query, plus update the model that comes from the request body accordingly. This way not only the field version will remains Ok, but other aspects as well.
A sample of updating in the Lambda handler function.
Note: You'll need to delete the property __typename
const updateRuralAddressRequest = JSON.parse(event.body);
const client = await appsyncClient.hydrated();
const ruralAddressQuery = await client.query({ query: gql(queryRuralAddress), variables: { id: updateRuralAddressRequest.id } });
const ruralAddressFromDb = ruralAddressQuery.data.getRuralAddress;
const updateRuralAddressInput = { ...ruralAddressFromDb, ...updateRuralAddressRequest };
delete updateRuralAddressInput.__typename;
const data = await client.mutate({ mutation: gql(updateRuralAddressMutation), variables: { updateRuralAddressInput } });
Notice that both my query and my update mutation must contains the version field.
export const queryRuralAddress = `
query GetRuralAddressByID($id: ID!) {
getRuralAddress(id: $id) {
id,
latitude,
longitude,
stateCity,
status,
_version
}
}
`;
export const updateRuralAddressMutation = `
mutation UpdateRuralAddress($updateRuralAddressInput:UpdateRuralAddressInput!) {
updateRuralAddress(input: $updateRuralAddressInput) {
id
latitude
longitude
stateCity
status,
_version
}
}`;
You’ll see an auto generated field “_version” in your dynamoDB Table. See the value of _version for the ID you want to update area field.when you fetch address,you'll get a field "_version".
Try Something like this
mutation UpdateAddressInput {
updateAddress(input:{
id:"af5cd7e6-8213-48b6-aa8e-9d2d6999742c",
area:"New Delhi",
_version:variable name // _version value in dynamoDB table for
// the desired row.
}){
id,
area
}
}

Graphql mutation query : how to access the data elements

const MUTATION_QUERY = gql`
mutation MUTATION_QUERY(
$name: bigint!
) {
insert_name(
objects: {
name: $name
}
) {
returning {
id
name
}
}
}
`;
const [onClick, { error, data }] = useMutation<{}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
My mutation query is inserting name in my table and autogenerating the id. On console logging the data variable I can view the fields id and name in the data object. But I am not able to access them them individually. How can I console.log "id". Thank you.
the console.log(data) looks like : {insert_name: {...}}
which expands to :
insert_name:
returning: Array(1)
0: {id: 1, name: 1234}
length: 1
_proto_: Array(0)
_typename: "insert_name_mutation_response
You can access the fields of an object with .
For example, if your object looks like this -
data = {
id: 1,
name: 'Jane',
}
You can get just the id with data.id
This works no matter how many layers deep your object may go, so take this example -
data = {
person: {
id: 1,
name: 'Jane',
}
}
You could get the id of person with data.person.id.
console.log(data.insert_name.returning[0].id) will give you the id returned.
For it to work in typescript we need to change the query to add the return type of data
const [onClick, { error, data }] = useMutation<{ReturnInsertNameProps}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
interface ReturnInsertNameProps {
insert_name: ReturnQueryProps;
}
interface ReturnProps {
returning: MessageProps[];
}
interface NameProps {
id: number;
name: number;
}
We can also use onCompleted method provided in useMutation if we want to process the result of the query.

How can I access all elements with a particular attribute in graphQL?

I have some json data in file called countryData.json structured as so:
{
"info":"success",
"stats":
[{
"id":"1",
"name":"USA",
"type":"WEST"
},
//...
I'm using graphQL to access this data. I have created an object type in the schema for countries using the following:
const CountryType = new GraphQLObjectType({
name: "Country",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
type: { type: GraphQLString },
})
});
I want to write a query that will allow me to access all of the elements of this array that have a certain "name" value(There can be multiple with the same name). I've written the following query, but it only returns the first match in the array:
const RootQuery = new GraphQLObjectType({
name:"RootQueryType",
fields:{
country: {
type: CountryType,
args: { type: { name: GraphQLString } },
resolve(parent, args){
return _.find(countryData.stats, {name: args.name});
}
}
}
});
The "_" comes from const _ = require('lodash');
Also, how can I just get every single item in the array?
I have not recreated the code, therefore I can not check if it would be executed correctly. This is code, that should work in my opinion (without trying). If you want to return array of elements you need to implement https://lodash.com/docs/#filter. Filter will return all objects from stats, which match the argument name. This will return correctly inside resolver function, however, your schema needs adjustments to be able to return array of countries.
You need probably rewrite the arguments as follows as this is probably not correct. You can check out how queries or mutation arguments can be defined https://github.com/atherosai/express-graphql-demo/blob/feature/2-json-as-an-argument-for-graphql-mutations-and-queries/server/graphql/users/userMutations.js. I would rewrite it as follows to have argument "name"
args: { name: { type: GraphQLString } }
You need to add GraphQLList modifier, which defines, that you want to return array of CountryTypes from this query. The correct code should look something like this
const RootQuery = new GraphQLObjectType({
name:"RootQueryType",
fields:{
country: {
type: CountryType,
args: { name: { type: GraphQLString } },
resolve(parent, args){
return _.find(countryData.stats, {name: args.name});
}
},
countries: {
type: new GraphQLList(CountryType),
args: { name: { type: GraphQLString } },
resolve(parent, args){
return _.filter(countryData.stats, {name: args.name});
}
}
}
});
Now if you call query countries, you should be able to retrieve what you are expecting. I hope that it helps. If you need some further explanation, I made the article on implementing lists/arrays in GraphQL schema as I saw that many people struggle with similar issues. You can check it out here https://graphqlmastery.com/blog/graphql-list-how-to-use-arrays-in-graphql-schema
Edit: As for the question "how to retrieve every object". You can modify the code in resolver function in a way, that if the name argument is not specified you would not filter countries at all. This way you can have both cases in single query "countries".

TypeScript - A function that returns a portion of the input

I'm new to Typescript and I want to write a function that returns the object it receives but with less info.
Let's say I have a Type like that:
type movie = {id: number, title:string, url:string, rating:number}
so I need a function that will receive an array of movies and return it only with id and title.
so if the input is:
[{id:1, title:Wonder Woman, url: www, rating:5},{id:2, title: Super
Man, url:www2, rating:7}]
I need the output to be like that:
[{id:1, title:Wonder Woman},{id:2, title: Super Man}]
Is there an easy way to do that?
You can signal to TS that you're working with part of a type (called a Partial) by using Partial<...>
type Movie = { id: string | number, name: string, url: string }
const movies: Movie[] = [
{ id: 1, name: "Movie A", url: "http://www.google.com" },
{ id: 2, name: "Movie B", url: "http://www.google.com" }
]
function stripUrl ({ id, name }: Movie): Partial<Movie> {
return { id, name }
}
const mMovies = movies.map(stripUrl)

Angular2 Mapping nested json array to model

I am not able to map the nested json array which is response from Web to my model array in Angular2. Suppose I have json array response as below:
[{
"base_url": "http://mysearch.net:8080/",
"date": "2016-11-09",
"lname": "MY PROJ",
"name": "HELLO",
"description": "The Test Project",
"id": 10886789,
"creationDate": null,
"version": "2.9",
"metrics": [{
"val": 11926.0,
"frmt_val": "11,926",
"key": "lines"
},
{
"val": 7893.0,
"frmt_val": "7,893",
"key": "ncloc"
}],
"key": "FFDFGDGDG"
}]
I was trying to manually map the fields referring the link Angular 2 observable doesn't 'map' to model to my model and was able to display those in my HTML by iterating through ngFor.....but I want to also display ncloc and lines value also in the HTML but I am not sure how to map those values to my Model array like mentioned in the above link.
Can you please help me with this?
Thanks.
EDIT
Mode class
export class DeiInstance {
base_url: string;
date: string;
lname : string;
name : string;
id : number;
key:string;
constructor(obj: DeiInstance) {
this.sonar_url = obj['base_url'];
this.lname = obj['lname'];
this.name = obj['name'];
this.id = obj['id'];
this.key = obj['key'];
this.date = obj['date'];
}
// New static method.
static fromJSONArray(array: Array<DeiInstance>): DeiInstance[] {
return array.map(obj => new DeiInstance(obj));
}
}
You can simplify your model and your mapping a lot.
You don't need to map your API response manually. JavaScript/TypeScript can do this for you.
First you need multiple interfaces.
export interface DeiInstance {
base_url: string;
date: string;
lname: string;
name: string;
description: string;
id: number;
creationDate: string; //probably date
version: string
metrics: Metric[];
key: string;
}
export interface Metric {
val: decimal;
frmt_val: decimal;
key: string;
}
You can then use the as-"operator" of TypeScript to cast your API response to the DeiInstance Type.
sealSearch(term: string): Observable<DeiInstance[]> {
return this.http.get(this.sealUrl + term)
.map(response => response.json() as DeiInstance[])
.catch(this.handleError);
}
If you use interfaces instead of classes you have also the advantage that you have less production code which will be sended to the client browser.
The interface is only there for pre-compile-time or however you want to call it.
Hope my code works and it solves your problem.

Resources