I presently have a page with a dynamically created form. I am having trouble understanding how to manipulate the state and GraphQL query to handle nested queries.
With my present implementation it does not seem to be able to create any new entries. I want to create 1 "target" with several sub "addr" tied to it in one mutation.
This is the state definitions:
state = {
name:'',
addr:[{
mobilepkg:'',
target_url:'',
target_ip: '',
idCars:[]
}],
category:'',
date: '',
location:''
}
Handler for Graph:
handleTarget = async e => {
e.preventDefault()
const { name,
target_url,
target_ip,category,
mobilepkg,date,location } = this.state
let idCars = this.state.idCars
let adras = this.state.addr
await this.props.createTargetMutation({
variables: {
data: {
name,
addr:{
create:
[{
target_url,
target_ip,
mobilepkg,
cars: {
connect: idCars
},
}]
},
date,
location,
category
}
}
})
this.props.history.replace('/targets')
}
}
My create mutation
const CREATE_DRAFT_MUTATION = gql`
mutation CreateTargetMutation($data: TargetCreateInput!) {
createTarget(data: $data) {
id
name
addr
category
}
}
`
GraphQL datamodel
type Target {
id: ID! #unique
name: String!
addr: [Addr!]!
category: String!
date:String!
location:String!
}
type Addr {
id: ID! #unique
target_url:String!
target_ip:String!
mobilepkg:String!
cars: [Car!]!
}
How do I put my ReactJS state which has a nested array into GraphQL?
PS:I am new to GraphQL and ReactJS.
EDIT: In playground im able to create my items but its not working in my actual application.
mutation CreateTargetMutation($data: TargetCreateInput!) {
createTarget(data: $data) {
id
name
addr{
target_ip
target_url
mobilepkg
cars{
id
}
}
category
date
location
}
}
{
"data": {
"name":"testerquery",
"addr": {
"create": {
"target_ip":"123",
"target_url":"123",
"mobilepkg":"asd",
"cars":{"connect":{"id":"cjs3yd83u004a0781jffzaqqr"}}
}
},
"category":"simple",
"date":"2019-03-12",
"location":"kl"
}
}
Bro, you are one the right path. You just need to iterate your values in order to solve this problem. Once you iterate through the values you simply need to make a call to the new array which contains everything and it will work. As your values are nested you will need to add "{}" to your car variable and within that contain your connect since you wish to create new "addr" whilst connecting to existing "car".
let create = []
for(let i=0; i < this.state.addr.length; i++){
create.push({
'mobilepkg':this.state.addr[i].mobilepkg,
'target_url':this.state.addr[i].target_url,
'target_ip':this.state.addr[i].target_ip,
'cars': {
'connect': this.state.addr[i].cars}
})
}
await this.props.createTargetMutation({
variables: {
data: {
name,
addr: {
create
},
category,
date,
location
}
}
})
this.props.history.replace('/targets')
}
}
Your values should now successfully pass into GraphQL and create targets with many "addr" whilst connecting to many "car"
Related
I have two models in Objection - "brands" and "offers".
Brand:
const { Model } = require('objection')
class Brand extends Model {
static get tableName() {
return 'brands'
}
...
static get relationMappings() {
const Offer = require('./offer-model')
return {
offer: {
relation: Model.HasManyRelation,
modelClass: Offer,
join: { from: 'brands.id', to: 'offers.brand_id' }
}
}
}
}
Offer:
const { Model } = require('objection')
class Offer extends Model {
static get tableName() {
return 'offers'
}
}
A brand has many offers, but I want to get brands which have at least 1 offer using withGraphFetched, excluding brands which have no offers. Here's what I have so far:
const brandModel = this.objection.models.brand
const query = brandModel.query().withGraphFetched('offer')
query.page(page, page_size)
const offers = await query
This returns the "joined" data, but also returns brands which don't have offers. For example:
[{
id:1,
name: 'brand 1',
offers: [{offerId: 1, offerName: 'offer 1'}]
},{
id:2,
name: 'brand 2',
offers: []
}]
In the above data, I don't want the brand with ID 2 to be in the result set.
I am using Objection/Knex to paginate the results, so I can't just exclude the brands with empty object arrays after the query has been executed.
I can achieve this using raw queries, but that means I can't use the Objection dynamic attributes and a few other key parts of Objection.
Thanks!
You can just tack a whereExists onto the query; something like
const query = brandModel.query()
.withGraphFetched('offer')
.whereExists(
(qb) => qb.select('id').from('offers')
.where('offers.brand_id', knex.ref('brands.id'))
);
Even though the whereExists bit is directly Knex, the query still goes through your models so stuff you've defined there should still apply (maybe unless you're doing something very wild that directly affects the columns used inside the whereExists)
Graphql schema:
type SDKConfig #model
#key(name: "byPublisher", fields: ["publisher_id", "id"]){
id: ID!
publisher_id: ID!
facebook_app_id: String
adjust_app_token: String
}
type GameConfig #model
#auth(rules: [
{allow: owner},
{allow: groups, groupsField: "groups"}]){
id: ID!
game_name: String!
bundle_identifier: String!
sdkConfigs: [SDKConfig] #connection(keyName: "byPublisher", fields: ["id"])
groups: [String]
}
Mutations:
export const createGameConfig = /* GraphQL */ `
mutation CreateGameConfig(
$input: CreateGameConfigInput!
$condition: ModelGameConfigConditionInput
) {
createGameConfig(input: $input, condition: $condition) {
id
game_name
bundle_identifier
sdkConfigs {
items {
id
publisher_id
facebook_app_id
adjust_app_token
createdAt
updatedAt
}
nextToken
}
groups
createdAt
updatedAt
owner
}
}
`;
React function:
async function createGame() {
try {
const newgame = {
"game_name": "deneme",
"bundle_identifier": "com.magiclab.deneme",
sdkConfigs: [
{ "publisher_id": 5,
"facebook_app_id": "fb12313",
"adjust_app_token": "adjusttoken123123",
}
]
}
await API.graphql(graphqlOperation(createGameConfig, {input: newgame}))
} catch (err) {
console.log('error creating game sdk config:', err)
}
}
Error message:
"The variables input contains a field name 'sdkConfigs' that is not defined for input object type 'CreateGameConfigInput' "
I want to create an array of objects within the object. How to fix input object for graphql ?
You should run two different mutations, one for creating the GameConfig and anorther one for create the SDKConfig it will be something like this
async function createGame() {
try {
const newgame = {
game_name: 'deneme',
bundle_identifier: 'com.magiclab.deneme',
};
const sdk = {
publisher_id: null,
facebook_app_id: 'fb12313',
adjust_app_token: 'adjusttoken123123',
};
const {
data: {
createGameConfig: { id: publisher_id },
},
} = await API.graphql(
graphqlOperation(createGameConfig, { input: newgame })
);
sdk.publisher_id = publisher_id;
await API.graphql(graphqlOperation(createSDKConfig, { input: sdk }));
} catch (err) {
console.log('error creating game sdk config:', err);
}
}
then you will use the id return by the first mutation as an input for the second mutation, this identifier will bound these two entries and when you query any gameConfig it will pull in an array any SDKConfig that their publisher_id matches with the gameConfig.
You could expand these information in this section of the official documentation https://docs.amplify.aws/cli/graphql-transformer/directives#belongs-to
I'm trying to delete a mongodb object and then once deleted, I want to delete everything associated with that mongodb object. Including nested mongodb objects from my mongo database.
var parentObjectSchema = new mongoose.Schema({
name: String,
split: Number,
parts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "ChildObjectSchema"
}
],
});
var childObjectSchema = new mongoose.Schema({
name: String,
number: Number,
things: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Things"
}
],
});
So I am trying to delete the parentObject, and childObjects that come along with it. Not sure how I would go about doing that. I am successful in deleting the parentObject but that childObject is still in the mongodb, taking up space. Any ideas?
MongoDB doesn't provide the notion of foreign keys like other databases do. Mongoose has convenience methods in the client library that populates your documents with other documents using multiple queries and joining the results:
https://mongoosejs.com/docs/populate.html
If you want to do a cascading deletion then you'll need to grab the object ids of the children in the parent documents you want to delete, and then execute a delete against those children documents.
Here's a simplified example:
const deleteThing = (thingId) => {
thingObjectSchema.remove({ _id: thingId });
};
const deleteChild = (childId) => {
childObjectSchema.findOne({ _id: childId }).select('things').lean().exec((err, child) => {
for (const thingId of child.things) {
deleteThing(thingId);
}
childObjectSchema.remove({ _id: childId });
})
};
const deleteParent = (parentId) => {
parentObjectSchema.findOne({ _id: parentId }).select('parts').lean().exec((err, parent) => {
for (const childId of parent.parts) {
deleteChild(childId);
}
parentObjectSchema.remove({ _id: parentId });
})
};
// note: not actually tested
I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256
I need to be able to create a user and add it's favourite movies (An array of objects with a reference to the Movies collection and his personal rating for each movie) in a single request.
Something that could look like this (pseudocode)
var exSchema = `
type Mutation {
addUser(
name: String!
favMovies: [{ movie: String! #ref to movies coll
personal_rating: Int! # this is different for every movie
}]
) : User
}
...
`
What is the graphql way of doing this in a single request? I know I can achieve the result with multiple mutations/requests but I would like to do it in a single one.
You can pass an array like this
var MovieSchema = `
type Movie {
name: String
}
input MovieInput {
name: String
}
mutation {
addMovies(movies: [MovieInput]): [Movie]
}
`
Then in your mutation, you can pass an array like
mutation {
addMovies(movies: [{name: 'name1'}, {name: 'name2'}]) {
name
}
}
Haven't tested the code but you get the idea
I came up with this simple solution - NO JSON used. Only one input is used. Hope it will help someone else.
I had to add to this type:
type Option {
id: ID!
status: String!
products: [Product!]!
}
We can add to mutation type and add input as follows:
type Mutation {
createOption(data: [createProductInput!]!): Option!
// other mutation definitions
}
input createProductInput {
id: ID!
name: String!
price: Float!
producer: ID!
status: String
}
Then following resolver could be used:
const resolvers = {
Mutation: {
createOption(parent, args, ctx, info) {
const status = args.data[0].status;
// Below code removes 'status' from all array items not to pollute DB.
// if you query for 'status' after adding option 'null' will be shown.
// But 'status': null should not be added to DB. See result of log below.
args.data.forEach((item) => {
delete item.status
});
console.log('args.data - ', args.data);
const option = {
id: uuidv4(),
status: status, // or if using babel status,
products: args.data
}
options.push(option)
return option
},
// other mutation resolvers
}
Now you can use this to add an option (STATUS is taken from first item in the array - it is nullable):
mutation{
createOption(data:
[{
id: "prodB",
name: "componentB",
price: 20,
producer: "e4",
status: "CANCELLED"
},
{
id: "prodD",
name: "componentD",
price: 15,
producer: "e5"
}
]
) {
id
status
products{
name
price
}
}
}
Produces:
{
"data": {
"createOption": {
"id": "d12ef60f-21a8-41f3-825d-5762630acdb4",
"status": "CANCELLED",
"products": [
{
"name": "componentB",
"price": 20,
},
{
"name": "componentD",
"price": 15,
}
]
}
}
}
No need to say that to get above result you need to add:
type Query {
products(query: String): [Product!]!
// others
}
type Product {
id: ID!
name: String!
price: Float!
producer: Company!
status: String
}
I know it is not the best way, but I did not find a way of doing it in documentation.
I ended up manually parsing the correct schema, since JavaScript Arrays and JSON.stringify strings were not accepted as graphQL schema format.
const id = 5;
const title = 'Title test';
let formattedAttachments = '';
attachments.map(attachment => {
formattedAttachments += `{ id: ${attachment.id}, short_id: "${attachment.shortid}" }`;
// { id: 1, short_id: "abcxyz" }{ id: 2, short_id: "bcdqrs" }
});
// Query
const query = `
mutation {
addChallengeReply(
challengeId: ${id},
title: "${title}",
attachments: [${formattedAttachments}]
) {
id
title
description
}
}
`;
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
}
}"
Pass them as JSON strings. That's what I do.
For those of you who don't need to pass in an array for one request, and are open to the idea of making a request for every mutation. (I am using Vue3, compisition Api, but React and Angular developers still can understand this).
You cannot for loop the mutation like this:
function createProject() {
for (let i = 0; i < state.arrOfItems.length; i++) {
const { mutate: addImplementation } = useMutation(
post_dataToServer,
() => ({
variables: {
implementation_type_id: state.arrOfItems[i],
sow_id: state.newSowId,
},
})
);
addImplementation();
}
}
this will give you an error, because the mutation must be in the setup().
(here is the error you will recieve: https://github.com/vuejs/vue-apollo/issues/888)
Instead create a child component, and map the array in the parent.
in Parent.vue
<div v-for="(card, id) in state.arrOfItems">
<ChildComponent
:id="id"
:card="card"
/>
</div>
in ChildComponent.vue
recieve props and:
const { mutate: addImplementation } = useMutation(
post_dataToServer,
() => ({
variables: {
implementation_id: props.arrOfItems,
id: props.id,
},
})
);