React-apollo: multiple mutations in one @graphql(gql`

Created on 28 Sep 2016  路  20Comments  路  Source: apollographql/react-apollo

is it possible somehow to use more then one mutation with just one graphql(gql`

I am trying to do something like

@graphql(gql`
  mutation loginViaFacebook($profile: JSON!, $signedRequest: String!) {
    user: loginViaFacebook(profile: $profile, signedRequest: $signedRequest) {
      firstName
      lastName
    }
  }

  mutation loginViaTwitter($profile: JSON!, $signedRequest: String!) {
    user: loginViaTwitter(profile: $profile) {
      firstName
      lastName
    }
`)

I am able to do that with two graphql but I am not sure if this is what we wanted.

@graphql(gql`
  mutation ($profile: JSON!, $signedRequest: String!) {
    user: loginViaFacebook(profile: $profile, signedRequest: $signedRequest) {
      firstName
      lastName
    }
  }
`, { name: 'loginViaFacebook' })
@graphql(gql`
  mutation ($profile: JSON!, $signedRequest: String!) {
    user: loginViaTwitter(profile: $profile) {
      firstName
      lastName
    }
  }
`, { name: 'loginViaTwitter' })
MyComponent

Most helpful comment

@migueloller @seeden @stubailo thanks for the open discussion around the API. Always looking to improve! I can give you some backstory into the current API and why I ended up going with a 1:1 relationship between document and HOC. The below and this will hopefully shed some light on the design

First run

When we first started out, react-apollo exported out a connect method which wrapped the connect from react-redux and used an object for an argument. The object allowed you to use all of the functionality of redux as well as two new additions: mapQueriesToProps and mapMutationToProps. Both of these keys expected a function which returned an object with an arbitrary amount of queries / mutations stored in them. They sought to have a shared prop shape between mutations and queries including loading and the data of the result.

This was really easy to integrate into existing redux codebases, but it shipped unneeded things to the client if you weren't using react-redux. It also made for largely nested result props with no way to adjust them. You would often end up with something like this: this.props.myQueryName.result.STUFF_I_ACTUALLY_WANT. Some improvements were made which moved the the STUFF_I_ACTUALLY_WANT to this.props.myQueryName.STUFF_I_ACTUALLY_WANT which is similar to where we are now.

Reducing boilerplate

The next issue that came up was the boilerplate needed to create even a single query. Given a query like so query myQueryName { foo { bar } }, you would need to create an function returning and object like so: const mapQueriesToProps = () => ({ myQueryName: { query: MYQUERY } }). This may not seem like a lot in this inline shorthand, but in practice, most queries + options became quite large.

The move to the new API sought to bring a simple and easy to quickly understand default. In fact, all of the designs behind the new API started with a simple expression of wrapping a component with a query.

MyQuery => MyComponent

graphql(gql`query myQueryName { foo { bar } }`)(MyComponent)

My biggest concern with this API is actually what this thread is about, i.e. multiple operations per component. Starting at the simplest use case, the next step was to look at some code bases and see how often multiple operations were used on a single component. What I found from our own semi large (and growing) app, as well as a few others, was that typically there was a 1:1 query to component usage. This was not the case for mutations however which lead to a few other improvements.

Understanding props and options

As apollo-client started to mature, a number of new (and awesome) api additions had begun to land. Most notable for this discussion was pagination via fetchMore and refetching via refetch. These methods can be used to do some great UI actions but often the terminology like refetch didn't match what was happening within the component (for instance a filter action). With the addition of { props = () => ({}) } as the second option to the HOC, you could really separate your UI components and your data actions in an awesome way. The props being passed to the UI made sense for that component.

Similarly, options in apollo-client became more robust with things like updateQuery and optimistic response. Each query in the original mapping could potentially use these options which made it difficult to read and understand how options related to their query. Binding a 1:1 option to HOC made this easier to understand.

Testing, code sharing, and future optimizations

One of the biggest benefits of the new API in my opinion, is the composition nature of it. This makes it really easy to export all of the parts to adequately test the full scope of your project. In fact, here is an example including some new tooling around this coming soon!

Thanks to the caching methods of apollo-client, you can reuse graphql() HOCs all over your app in an efficient manner. A single USER or THING enhancer can be paired with a number of different components. I like to think of them like stateless data components in terms of their ability to join in different manners to create new blocks. Having individual containers also means you can easily use data from one query, to make a request to another query. It also makes it easier to join with common react libraries like redux-form and react-redux.

In the future, having static queries as we do, makes it easier to create integrated build tooling like relay has, to parse you code base and send a whitelist of operations to your server while reducing code shipped to the client. It also makes static typing more possible via flow / typescript.

Counter argument

From a technical perspective, none of the above is actually impossible in a object map API instead of the current 1:1 operation:HOC. What does get more difficult is the internal code base to handle multiple operation types (mutations + subscriptions + queries) in a single component as well as the visual understanding in the code base to know what is going on.

Luckily GraphQL itself makes some of this easier for us. Given your example:

graphql(gql`
  query UserQuery ($userId: ID!) {
    user(id: $userId) {
      # ... selection set
    }
  }

  query PostQuery($postId: ID!) {
    post(id: $postId) {
      # ... selection set
    }
  }

  mutation CreateUser ($createUserInput: CreateUserInput!) {
    createUser(data: $createUserInput) {
      # ... selection set
    }
  }

  mutation CreatePost ($createPostInput: CreatePostInput!) {
    createPost(data: $createPostInput) {
      # ... selection set
    }
  }
`, {
  props: ({ data: { loading, user, post }, createUser, createPost }) => {
    // ... do stuff with multiple operations
  }
});

The first part could be rewritten as

graphql(gql`
  query UserAndPostQuery ($userId: ID!, $postId: ID!) {
    user(id: $userId) {
      # ... selection set
    }
    post(id: $postId) {
      # ... selection set
    }
  }
`, {
  props: ({ data: { loading, user, post } }) => {
  }
});

That leaves the joined mutations turning into something like this

@graphql(gql`
  mutation CreateUser ($createUserInput: CreateUserInput!) {
    createUser(data: $createUserInput) {
      # ... selection set
    }
  }
`, { name: "createUser" })
@graphql(gql`
  mutation CreatePost ($createPostInput: CreatePostInput!) {
    createPost(data: $createPostInput) {
      # ... selection set
    }
  }
`, { name: "createPost" })

I guess that the options object would need to be a map by operation name.

Originally the new API auto mapped each query based on the operation name and used data if none was present. What I found from looking at a lot of queries, the operation name was more a debug tooling use than actually what you would use in props. A lot of operation names tend to be PascalCase by convention, whereas most js devs use camelCase so using operation names in actual code ended up feeling more odd than not.

Its also worth noting that a GraphQL server can only accept one operation at a time. This makes copy pasting between graphiql and react-apollo a better experience in my thoughts.

Next steps

  • First, I think I need to try to articulate a design document which explains the goal of this integration.
  • Second, I think the docs should highlight different composition methods like compose(....), decorators, and plain composition
  • Third, I'm always open to reevaluating and exploring enhanced support. There is a good what works for my team is not what works for others or even most! This library was built for our use cases / design principles which aren't everyone's!

The next major focus for me, fwiw, is on adding awesome support for subscriptions in graphql and really improving the development / knowledge base around this project. This is start of the latter. After that is taking another stab at fragment composition and query preloading. Then, the next tentative focus is on a custom SSR render which supports streaming so time to first byte is amazingly fast.

Sorry if that was too long of a response! I hope its helpful!

All 20 comments

Yep that's the recommended way to do it! Mind submitting a PR to the docs to clarify?

@stubailo thank you for your answer 馃憤
Yep there is no information about it in the docs. Do we have any special reason why we cannot support two and more mutations in one graphql? I have one second question. Next two examples are identical

mutation SOMETHING($profile: JSON!) {
    user: loginViaFacebook(profile: $profile) {
      firstName
    }
  }
mutation ($profile: JSON!) {
    user: loginViaFacebook(profile: $profile) {
      firstName
    }
  }

Can we use "SOMETHING" as name of the props instead of mutate? I would like to use

mutation loginViaFacebook($profile: JSON!) {
    user: loginViaFacebook(profile: $profile) {
      firstName
    }
  }

instead of

@graphql(gql`
  mutation ($profile: JSON!) {
    user: loginViaFacebook(profile: $profile) {
      firstName
      lastName
    }
  }
`, { name: 'loginViaFacebook' })

Or we are using it for anything else?

The operation names are important for debugging purposes, and we are thinking of using them to power Redux-style actions in the next iteration of the API. Perhaps it would also make sense to use them for the name of the prop.

Do we have any special reason why we cannot support two and more mutations in one graphql?

I mean, right now each container passes down exactly one function. So this would require passing down two functions if you want to be able to call those mutations independently. You can already put multiple root fields in one mutation, but it doesn't seem like much overhead to just add two decorators.

@stubailo, don't you think it would be better to be able to pass an arbitrary number of operations as props, though? It feels like the intuitive API would be to pass just one GraphQL query to the graphql HoC and then expose query operations via the data.operationName prop and expose mutation operations via operationName props?

That way one could potentially do something like this:

graphql(gql`
  query UserQuery ($userId: ID!) {
    user(id: $userId) {
      # ... selection set
    }
  }

  query PostQuery($postId: ID!) {
    post(id: $postId) {
      # ... selection set
    }
  }

  mutation CreateUser ($createUserInput: CreateUserInput!) {
    createUser(data: $createUserInput) {
      # ... selection set
    }
  }

  mutation CreatePost ($createPostInput: CreatePostInput!) {
    createPost(data: $createPostInput) {
      # ... selection set
    }
  }
`, {
  props: ({ data: { loading, user, post }, createUser, createPost }) => {
    // ... do stuff with multiple operations
  }
});

Would something like this work for you @seeden? It's something I was have actually been wanting to request myself.

What if you need to pass some options though? Also, this means Apollo is doing operation splitting for you, which will make it harder to do persisted queries, for example.

Is this concretely better other than having to type fewer characters?

I guess that the options object would need to be a map by operation name. If there's a single anonymous operation then the API could behave like it does now, if there's multiple operations, the options object has to be a map where each key has to be the name of the operations defined.

I'm not sure what persisted queries are so I can't comment on that.

Other than having to type a fewer characters it brings the benefit of sending everything in a single trip to the server. Yes, you could do query batching but I've had some issues using query batching and fragments with Apollo. This way you would only send a single query to the GraphQL server instead of two.

I think the Apollo API is great by the way! This is something that is definitely and added bonus. But there is definitely benefit with being able to declare the needs in a single query and to support the full extent of what a GraphQL document is. It just feels right that for completion one could pass any valid document that GraphiQL could take into the graphql HoC.

For context we used to have an HoC that would take an object with multiple operations but it ended up being quite complicated to use. Perhaps we can work back up to that though.

@stubailo,

Ah I see. How complex was the API? Was it much different from the current one?

I think that adding support for multiple operations could be a non-breaking change. Basically if given 1 anonymous operation behave like it is now but if given multiple operations, expect options to be a map of options (helpful error messages similar to those used in graphql-tools can let people know when they've specified a key for an operation that doesn't exist).

With regards to how the props will be named for each operation, it will take some thought because mutate only maps well to a single operation. Maybe if mutate was an object (on the multiple operation case), each key could correspond to each mutation operation. For queries, they could be put inside the data field.

I'm just throwing these out there but I'm sure you guys have thought about this for a long time and probably have some better APIs that have come up. Also, appreciate the prompt responses!

@migueloller @seeden @stubailo thanks for the open discussion around the API. Always looking to improve! I can give you some backstory into the current API and why I ended up going with a 1:1 relationship between document and HOC. The below and this will hopefully shed some light on the design

First run

When we first started out, react-apollo exported out a connect method which wrapped the connect from react-redux and used an object for an argument. The object allowed you to use all of the functionality of redux as well as two new additions: mapQueriesToProps and mapMutationToProps. Both of these keys expected a function which returned an object with an arbitrary amount of queries / mutations stored in them. They sought to have a shared prop shape between mutations and queries including loading and the data of the result.

This was really easy to integrate into existing redux codebases, but it shipped unneeded things to the client if you weren't using react-redux. It also made for largely nested result props with no way to adjust them. You would often end up with something like this: this.props.myQueryName.result.STUFF_I_ACTUALLY_WANT. Some improvements were made which moved the the STUFF_I_ACTUALLY_WANT to this.props.myQueryName.STUFF_I_ACTUALLY_WANT which is similar to where we are now.

Reducing boilerplate

The next issue that came up was the boilerplate needed to create even a single query. Given a query like so query myQueryName { foo { bar } }, you would need to create an function returning and object like so: const mapQueriesToProps = () => ({ myQueryName: { query: MYQUERY } }). This may not seem like a lot in this inline shorthand, but in practice, most queries + options became quite large.

The move to the new API sought to bring a simple and easy to quickly understand default. In fact, all of the designs behind the new API started with a simple expression of wrapping a component with a query.

MyQuery => MyComponent

graphql(gql`query myQueryName { foo { bar } }`)(MyComponent)

My biggest concern with this API is actually what this thread is about, i.e. multiple operations per component. Starting at the simplest use case, the next step was to look at some code bases and see how often multiple operations were used on a single component. What I found from our own semi large (and growing) app, as well as a few others, was that typically there was a 1:1 query to component usage. This was not the case for mutations however which lead to a few other improvements.

Understanding props and options

As apollo-client started to mature, a number of new (and awesome) api additions had begun to land. Most notable for this discussion was pagination via fetchMore and refetching via refetch. These methods can be used to do some great UI actions but often the terminology like refetch didn't match what was happening within the component (for instance a filter action). With the addition of { props = () => ({}) } as the second option to the HOC, you could really separate your UI components and your data actions in an awesome way. The props being passed to the UI made sense for that component.

Similarly, options in apollo-client became more robust with things like updateQuery and optimistic response. Each query in the original mapping could potentially use these options which made it difficult to read and understand how options related to their query. Binding a 1:1 option to HOC made this easier to understand.

Testing, code sharing, and future optimizations

One of the biggest benefits of the new API in my opinion, is the composition nature of it. This makes it really easy to export all of the parts to adequately test the full scope of your project. In fact, here is an example including some new tooling around this coming soon!

Thanks to the caching methods of apollo-client, you can reuse graphql() HOCs all over your app in an efficient manner. A single USER or THING enhancer can be paired with a number of different components. I like to think of them like stateless data components in terms of their ability to join in different manners to create new blocks. Having individual containers also means you can easily use data from one query, to make a request to another query. It also makes it easier to join with common react libraries like redux-form and react-redux.

In the future, having static queries as we do, makes it easier to create integrated build tooling like relay has, to parse you code base and send a whitelist of operations to your server while reducing code shipped to the client. It also makes static typing more possible via flow / typescript.

Counter argument

From a technical perspective, none of the above is actually impossible in a object map API instead of the current 1:1 operation:HOC. What does get more difficult is the internal code base to handle multiple operation types (mutations + subscriptions + queries) in a single component as well as the visual understanding in the code base to know what is going on.

Luckily GraphQL itself makes some of this easier for us. Given your example:

graphql(gql`
  query UserQuery ($userId: ID!) {
    user(id: $userId) {
      # ... selection set
    }
  }

  query PostQuery($postId: ID!) {
    post(id: $postId) {
      # ... selection set
    }
  }

  mutation CreateUser ($createUserInput: CreateUserInput!) {
    createUser(data: $createUserInput) {
      # ... selection set
    }
  }

  mutation CreatePost ($createPostInput: CreatePostInput!) {
    createPost(data: $createPostInput) {
      # ... selection set
    }
  }
`, {
  props: ({ data: { loading, user, post }, createUser, createPost }) => {
    // ... do stuff with multiple operations
  }
});

The first part could be rewritten as

graphql(gql`
  query UserAndPostQuery ($userId: ID!, $postId: ID!) {
    user(id: $userId) {
      # ... selection set
    }
    post(id: $postId) {
      # ... selection set
    }
  }
`, {
  props: ({ data: { loading, user, post } }) => {
  }
});

That leaves the joined mutations turning into something like this

@graphql(gql`
  mutation CreateUser ($createUserInput: CreateUserInput!) {
    createUser(data: $createUserInput) {
      # ... selection set
    }
  }
`, { name: "createUser" })
@graphql(gql`
  mutation CreatePost ($createPostInput: CreatePostInput!) {
    createPost(data: $createPostInput) {
      # ... selection set
    }
  }
`, { name: "createPost" })

I guess that the options object would need to be a map by operation name.

Originally the new API auto mapped each query based on the operation name and used data if none was present. What I found from looking at a lot of queries, the operation name was more a debug tooling use than actually what you would use in props. A lot of operation names tend to be PascalCase by convention, whereas most js devs use camelCase so using operation names in actual code ended up feeling more odd than not.

Its also worth noting that a GraphQL server can only accept one operation at a time. This makes copy pasting between graphiql and react-apollo a better experience in my thoughts.

Next steps

  • First, I think I need to try to articulate a design document which explains the goal of this integration.
  • Second, I think the docs should highlight different composition methods like compose(....), decorators, and plain composition
  • Third, I'm always open to reevaluating and exploring enhanced support. There is a good what works for my team is not what works for others or even most! This library was built for our use cases / design principles which aren't everyone's!

The next major focus for me, fwiw, is on adding awesome support for subscriptions in graphql and really improving the development / knowledge base around this project. This is start of the latter. After that is taking another stab at fragment composition and query preloading. Then, the next tentative focus is on a custom SSR render which supports streaming so time to first byte is amazingly fast.

Sorry if that was too long of a response! I hope its helpful!

@jbaxleyiii,

First of all, don't be sorry on the long response, it was awesome! Not many people care to be as detailed in their response and it really helps (or at least it helped me) understand what you're trying to communicate.

First off, I want to commend you guys on the great work in the entire Apollo stack. Super awesome work and I'm glad to be using it in production for our product (React Native app). I'm excited to see the new things coming up and would honestly love to contribute (one of the things I wanted to eventually suggest was to merge graphql-utilities into graphql-tools, for example, but wanted to understand what the future plans were with graphql-tools first).

Second, awesome example with the Articles link. I think this entire folder (component, tests, etc.) is a gold mine and really shows how React has made true separation of concerns via components a reality (something you mention in the Medium post you linked about the new API).

Finally, extremely excited for #240! I'm looking forward to all the changes.

To conclude, I do see the benefits in keeping the API restricted and forcing people to compose their HoCs (I'm a big user of recompose myself). It also helps drive home the idea that Joseph Savona started with Relay that I think applies to Apollo as well (or any React + GraphQL implementation really), and that is that if React is view = f(props), then React + GraphQL is view = f(query(props)).

Anyhow, my response got a bit long so I'll stop here. Really appreciate you taking the time for such a detailed response and please let me know if you're looking for contributors. Would love to help in any way I can!

@migueloller thats awesome you are using it in a production RN app! Thanks for the thoughtful reply! I'm glad it made some sense haha.

I'd love more contribution help for sure! Its one the biggest driving forces behind #240! I think once that has developed a little bit more, I can start outlining some next steps and ideas for future goals!

@jbaxleyiii I use graphQL to control IoT in my React-Native testing project and display collected IoT Data in my company's production project.

My experience is that this API is OK for now, It force me to separate mutations into different small Components, which in fact will increase the reusability, I think.

For anyone that finds this in the future (since this is the first result for "react apollo multiple mutations") the docs are now very helpful about how to format this.

I'm processing excel spreadsheets to batch create data in GraphQL.

A naive approach (the one I'm currently usinig) is to fire off all the mutations (1000+) and wait for the Promises to resolve. With the browser limiting network requests to 6 this takes a long time to complete.

My mutations are created with variables so its not easy to batch these together. I think from the discussions it would require parsing the graphql to rename variables so they are unique per muation and then provide these rebound names in as variables.

Its possible I'm doing this the wrong way :) It would be handy to know what the preferred ways are and to document them.

@baerrach this repo is specifically for react integration; but sounds like you should be able to leverage batching to send all of them in one request: http://dev.apollodata.com/tools/apollo-server/requests.html

Thanks, I am using the graphql HOCs. How can I make them do the batching, or do I need to do this manually instead?

I'm processing excel spreadsheets to batch create data in GraphQL.

And you're doing this with react somehow? That's interesting!

If you use a batching network interface it should happen automatically: http://dev.apollodata.com/core/network.html#query-batching

The react side isn't terribly interesting, the data upload is one small screen of the rest of the app.

Turning on the batched interface inserts "magic happens here"

It's still slow - but that looks like the backend database being thrashed. I can see from Chrome's Network dev tool that there are many less connections and the size of the request is larger.

I guess I was uncertain to try turning batching in on because the documentation all says "Query batching" there was one bit that mentioned mutations. Thanks.

Mind sending a PR to improve the docs?

Was this page helpful?
0 / 5 - 0 ratings