How do I add an array of Objects to a mutation in apollo-react?


#1

It seems like it would be so simple but I’ve spent all afternoon flailing.

I have outlined the exact problem on stack overflow here https://stackoverflow.com/questions/45253671/add-an-array-of-objects-to-a-mutation-in-apollo-react

Any help at all would be much appreciated.

Thanks


#2

To be able to fill in the ??? in this line you need to inspect your generated GraphQL schema. I lined out how you can do this here and here.

As the automatically generated API takes your data schema into account, including it in questions like these is super helpful.


#3

Thanks @nilan, sorry for not including the schema. The schema for the Tutorial type is:

  author: String
  completed: Boolean
  link: String
  title: String!
  id: ID! @isUnique
  createdAt: DateTime!
  updatedAt: DateTime!
  postedBy: User @relation(name: "UsersTutorials")

It doesn’t have any fields related to the tags yet. That’s the question though, I don’t know what type to add for an array of objects.

Each object has one number and one string, but I don’t want to create a whole new type and link them up. Is it not possible to just store an array of objects in a field in graphQL?


#4

This is exactly what types are for :slightly_smiling_face: You could add a new Tag type and add a many-to-many relation between Tutorial and Tag like so:

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

#5

Thanks @Nilan, I appreciate the help. I don’t really need tags to be a whole new data type. There is no need to interlink them. I just wanted to add an array of tags objects as a field to the tutorial type. It seems tremendously complicated, yet such a simple things that needs to be done so often in javascript.

However, accepting that I must create a new type, I edited the schema like you suggest (which makes complete sense) but I get stuck again trying to add multiple tags types in a mutation. Every example of mutations I can find in the graphcool or apollo docs only adds a single piece of data to a field, never multiple. Can you point me to an example of a mutation that adds a list of data to graphcool?

My mutation handler that I call on submit at the moment is…

const { author, link, title, tags } = this.state.tut
this.props.createTutorialMutation({
      variables: {
        author,
        link,
        title,
        tags,
        completed: false,
        postedById
      }
    })

where the tags in state is an array of objects like so…

[{id:1, text:"#abc"}, {id:2, text:"#123"}]

Then the mutation itself looks like so…

const CREATE_TUTORIAL_MUTATION = gql`
  mutation CreateTutorialMutation(
    $author: String
    $link: String
    $title: String!
    $tags: [Tag]
    $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
      }
    }
  }
`

But the error I get is GraphQL error: Variable '$tags' cannot be non input type '[Tag]'

I’m currently working on trying figure out what that means and how to fix it. Any pointers at all are super welcome :slight_smile:


#6

I’m sorry, obviously I should have added the mutation in my initial answer already! You can accomplish what you want without creating a new type and it comes with certain benefits and downsides, but more to that later.

Brace yourself, there’s a lot of text :smiley:

Modelling tags with a separate type

If you have a separate Tag type with a relation to Tutorial, the syntax of your createTutorial mutation depends on whether you want to create a new tag, or link to an existing one instead. The former is a nested create mutation while the latter is a nested connect mutation.

Nested Create

Let’s assume you want to create new tags, then you need to use the input argument tags: [TutorialtagsTag!]!. The generated type might be surprising at first, but there are good reasons it’s not [Tag]. In GraphQL, input and output types are different concepts, and Type is already used as an output type for the allTags and Tag queries, for example. So you need to update your variable in the mutation with this:

$tags: [TutorialtagsTag!]

or

$tags: [TutorialtagsTag!]!

The docs briefly mention how this type name is generated, and how you can explore it in the GraphQL Playground, but here and here are other posts where I talked more about that.

Note that the id is not part of this input object type, because it will automatically be generated. Your tags object needs to look like this instead:

const tags = [{text:"#abc"}, {text:"#123"}]

or like this, if number is also part of your schema:

const tags = [{text:"#abc", number: 1}, {text:"#123", number: 2}]

Nested Connect

If you want to link to existing tags instead, you need to use the tagsIds: [ID!] argument and provide a list of ids:

$tagsIds: [ID!]
const tagsIds = ["some-tag-id", "another-tag-id"]

Combined

You can also use tags and tagsIds in the same mutation, this would connect the new Tutorial node to all the tags in tagsIds and connect it to the new tags in tags. This is what you want to do if you want to only allow tags with a unique text, so for a new Tutorial, there will likely be some tags that already exists, and some that need to be created.

Nested Update

As a side note, I can imagine that with your current tags object, you tried to do a nested update actually. Unfortunately, that’s not yet supported. We’re currently discussing the addition of nested updates, upserts and appends.

Modelling tags with JSON

Instead of creating a new type, you can also use a tags: [Json!]! or tags: Json! field instead, so the tag information is stored separately for every tutorial. The different between Json! and [Json!]! is very subtle, you can have a look at this example to see the difference in usage.

Note that, if you only would need to store a list of strings compared to a list of objects containing a string AND an integer, you could also use a [String!]! field instead, but it has pretty much the same characteristics as using Json!.

Let’s have a look at the benefits and drawbacks that I could come up with.

Benefits

Here are the benefits of using JSON instead of a separate type:

  • it’s very simple. if you want to create a new tutorial with certain tags, you just compute the right JSON value and set it. With a separate type, you first need to query the ids of the existing tags, prepare the input object for creating new tags and use them as GraphQL variables for the mutation
  • it’s very flexible (this is not so relevant here because you have a fixed structure anyway, but might be interesting in other cases). A separate type imposes a fixed structure of fields.
  • there is no additional “clutter” in your GraphQL API because no additional mutations or queries are generated. A lot of queries and mutations are added by the new type and relation.

Neutral

  • the input JSON is validated. A separate type leverages the GraphQL type system for validation which is arguably better.

Drawbacks

I see the following drawbacks. They basically boil down to the fact that there is no first-level concept of a Tag in your data schema/GraphQL API, combined with the limited options of the Json field type:

  • another perspective on the missing “API clutter” is less functionality. It suddenly becomes very inefficient to obtain a list of all different tags, for example. With a separate Tag type you can run:

    query {
      allTags {
        id
        text
      }
    }
    

    which is pretty straight-forward. Another example would be adding a new tag to three different tutorials. You would need to query the current JSON, append a new object inside the JSON, and call the updateTutorial mutation. With a separate Tag type, you can use a nested connect mutation to do the same:

    mutation {
      createTag(tutorialsIds: ["some-tutorial-id", "another-tutorial-id"]) {
        id
      }
    }
    
  • there are no filters for JSON fields yet, (see according feature request) so obtaining all tutorials with the tag graphql for instance is not possible. With a separate Tag type, we can leverage a simple relation filter to accomplish this:

    query {
      allTutorials(filter: {
        tags_some: {
          text: "react"
        }
      }) {
        id
      }
    }
    

    Counting tutorials for a given tag is also not possible, which is rooted in the same limitation:

    query {
      _allTutorialsMeta(filter: {
        tags_some: {
          text: "react"
        }
      }) {
        count
      }
    }
    
  • you cannot reference to the same tag from different tutorials, you need to “inline” all the tag information in the Tutorial type itself, leading to increased data usage and the increased likelihood of data inconsistencies (think of renaming or removing a certain tag, for example)

Conclusion

With all of this said, as always, the best approach depends on your use case. I kind of assumed you want to do all the fancy stuff in some of the queries or mutations I mentioned. If that’s not the case, then by all means go ahead and use Json!, it certainly has its benefits.

But for me, the bottom line is that using a separate type adds some overhead that is outweighted by the additional possibilities it offers.


#7

It works! Thank you so much, that was incredible. This is going to take me a while to wrap my head around. Thank you.


#8

This is great. But to have a full “tag” support there is a need for upsert . Since it’s not yet supported, is there a way to manually check if the tag exists before creating a new one and if does, just reference it in the mutation?


#9

Well, you can query it using the allTags query for example :slight_smile:


#10

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