React-apollo: composing multiple/reusable mutation components?

Created on 29 Mar 2018  路  17Comments  路  Source: apollographql/react-apollo

Intended outcome:

We want to use the Mutation component, but conditionally execute on two Mutations (dependent on a prop received by the component it wraps).

I can't find documentation or use-cases that demonstrate this. Would this be a better use case for a HOC using graphql? or should I have a totally separate mutation function (that does A + B) rather than have one mutation that does A and maybe B?

Actual outcome:

<Mutation
        mutation={createMutation}
    >
        {(createNode) => {           
                const maybeCreateNewNodes = (propData) => 

                createNode({
                    variables: {
                        nodes: [newFolderNode],
                        options,
                    },
                });
               if (propData.specialProp) {
                   doSecondMutation({ variables: { specialProp }})
               }
            };

            return (
                <NewtemContainer
                    twoMutations={maybeCreateNewNodes}
                    {...props}
                />
            );
        }}
    </Mutation>

Stacking Mutation components doesn't seem to be a great use-case if I can just do

yield call (firstMutation)
if (specialProp)
yield call(secondMutation)

Version

docs feature high-priority

Most helpful comment

I'm also interested in something very close to this. I have a table with items and the user can perform CRUD operations on any of the items. I was thinking it might be nice to pass in an object instead of just a single mutation query.

Something like:

<Mutation mutations={ addItem: ADD_ITEM, deleteItem: DELETE_ITEM }>
  {({ addItem, deleteItem }) => { ... }
</Mutation>

All 17 comments

Multiple mutations seems to have been part of previous versions and also in previous docs. E.g. in the discussion of #238, frehner is even linking to http://dev.apollodata.com/react/mutations.html#multiple-mutations that seems to have been a full section about handling multiple mutations in the docs. In the current docs there is no mentions about this. Seems unfair :)

I'm also interested in something very close to this. I have a table with items and the user can perform CRUD operations on any of the items. I was thinking it might be nice to pass in an object instead of just a single mutation query.

Something like:

<Mutation mutations={ addItem: ADD_ITEM, deleteItem: DELETE_ITEM }>
  {({ addItem, deleteItem }) => { ... }
</Mutation>

My approach to this has been to use a combination of the graphql HOC referencing the mutation with props inside of the Mutation component. This works, but it would be much nicer to have the option of an object in the 'mutations' property listing multiple mutations has @brennancheung mentioned.

Has there been any work on this feature? I'm trying to create my own component that uses the same format @brennancheung laid out but I'm banging my head against the wall trying to get it to work.

Ok. I managed to get something working. It's probably horribly inefficient but it works. I'm using graphql() behind the scenes:

import React from 'react';
import {graphql, compose} from 'react-apollo';

export default function Graphql(mutationProps) {
    const {mutations, queries, render} = mutationProps;

    const composedMutationFuncs = compose(...Object.keys(mutations).map(v => {
        const {mutation, options} = mutations[v];
        return graphql(mutation, {
            name: v,
            alias: v,
            options,
        });
    }));

    const composedQueryFuncs = compose(...Object.keys(queries).map(v => {
        const {query, options} = queries[v];
        return graphql(query, {
            name: v,
            alias: v,
            options,
        });
    }));

    const SubCompInstance = compose(composedQueryFuncs, composedMutationFuncs)(subCompProps => {
        const mutates = subCompProps.mutationNames.reduce((memo, mutationName) => ({
            ...memo,
            [mutationName]: subCompProps[mutationName],
        }), {});

        const data = subCompProps.queryNames.reduce((memo, queryName) => ({
            ...memo,
            [queryName]: subCompProps[queryName],
        }), {});

        const loading = subCompProps.queryNames.reduce((memo, queryName) => memo || subCompProps[queryName].loading, false);

        return subCompProps.render(mutates, data, {loading});
    });

    return <SubCompInstance render={render} mutationNames={Object.keys(mutations)} queryNames={Object.keys(queries)}/>;
}

I use it like this:

function MyComp(props) {
  const mutations = {
    myFirstMutation: {mutation: gql`.....`},
    mySecondMutation: {mutation: gql`.....`, options: {}},
  };

  const queries = {
    myFirstQuery: {
      query: gql`...`,
      options: {
        variables: {someVar: 2},
      },
  }

  return (
    <Graphql mutations={mutations} queries={queries} render={(mutates, data, {loading}) => {
      // loading is true if any of the queries are currently loading.
      if (loading) return <div>Loading...</div>;
      // I can call mutates.myFirstMutation({variables: foo});
      return <div>{data.myFirstQuery.myquery.bar}</div>;
    }}/>
  );
}

It mimics the render prop style of and (although I called it render here, you can rename it to children if you want).

All the suggestions above ignore many parts of the new render prop components, e.g. onError, onCompleted, update, handling loading, and errors etc so doesnt make much sense. Its easier to look for a solution around the react render prop pattern than from apollo, e.g. https://github.com/jamesplease/react-composer . Maybe in the future apollo will publish similar to compose for hocs, e.g. Compose component for that.

If you mean conditionally changing the gql query based on a prop, I just used a variable to hold the query, and conditionally changed it based on the prop.

...
import { ADD_BUILD, EDIT_BUILD } from '../../../data/gql_queries/builds'
... 
render() {
  const { is_editing } = this.props
  let mutation_query
  if (is_editing) {
      mutation_query = EDIT_BUILD
      build['id'] = current_build.id // add id to the build object (from db), if editing
     }
  else {
      mutation_query = ADD_BUILD
    }

...
  return (
    <Mutation
          mutation={ mutation_query }
          variables={ {input: build} }
        >
          {(query_name, {data}) => {
  )

}

Any news on this ?
brennancheung's idea looks perfect to my eyes...

I have a component which can do 2 different actions, create and delete and I really don't want / can't to split them into different components as in my case I hand 2 those mutations into 2 callback props to a library. <MyLibrary createAction={callBackToMutation} deleteAction={callbackToMutation} />

The idea above would just need a little enhancement to handle other props as update. (such as <Mutation mutations={ addItem: {mutation: ADD_ITEM, update: () => ..}, deleteItem: {mutation: DELETE_ITEM, update: () => ...} }>)

Or it could even be a new component Mutations wich take as props named mutations:

<Mutations
  addItem={mutation: ADD_ITEM, update: () => ...}
  deleteItem={mutation: DELETE_ITEM, update: () => ...}
>
  {({ addItem, deleteItem }) => { ... }
</Mutations>

Anyhow that'd be great to have any working solution beside nesting Mutations ...

Just found react-adopt, which does almost this exact trick for me.

Edit: They have even adressed that very issue: https://github.com/pedronauck/react-adopt#leading-with-multiple-params

Any update on this? Something similar to @brennancheung solution would be ideal

Kind of hoping Hooks makes this a no-op as the false hierarchy would disappear.

I'm also using react-adopt to solve this issue. Take a look at this example with Apollo: https://codesandbox.io/s/3x7n8wyp15

Hi everyone! I believe that the new Apollo hooks, useQuery, useMutation, and useSubscription, adequately address this use case. To demonstrate this, I have converted @Cridda's example that uses react-adopt and modified it to use @apollo/react-hooks here: https://codesandbox.io/s/apollo-and-react-hooks-4vril

This example is by no means perfect, but it serves as a demonstration of how hooks can massively simplify some use cases.

If anyone still wants to pursue a new feature request from this library, I recommend that you make a post here: https://github.com/apollographql/apollo-feature-requests/issues

Thanks!

Here's a small helper if you don't/can't use @apollo/react-hooks

import React from 'react';
import { Mutation } from 'react-apollo';
import Composer from 'react-composer';
import PropTypes from 'prop-types';

const MultipleMutations = ({ mutations, children }) => (
  <Composer
    components={mutations.map(mutation => ({ render }) => (
      <Mutation {...mutation}>
        {(mutationFn, mutationState) => render({
          execute: mutationFn,
          ...mutationState,
        })}
      </Mutation>
    ))}
  >
    {children}
  </Composer>
);

MultipleMutations.propTypes = {
  mutations: PropTypes.arrayOf(PropTypes.object).isRequired,
  children: PropTypes.func.isRequired,
};

export default MultipleMutations;

Usage

<MultipleMutations
  mutations={[
    { mutation: MUTATION_1, variables: { foo: 'bar' } },
    { mutation: MUTATION_2 },
  ]}
>
  {([ mutation1, mutation2 ]) => (
    <>
      <button disabled={mutation1.loading} onClick={() => mutation1.execute({ ... })} />
      <button disabled={mutation2.loading} onClick={() => mutation2.execute({ ... })} />
    </>
  )}
</MultipleMutations>

Thank you @bitriddler

Thanks @bitriddler This is awesome!! ... For anyone else looking I had a requirement to refetchQueries

const MultipleMutations = ({ mutations, children }) => (
  <Composer
    components={mutations.map((mutation) => ({ render }) => (
      <Mutation {...mutation}> 
        {(mutationFn, mutationState) =>
          render({
            execute: mutationFn,
            ...mutationState,
          })
        }
      </Mutation>
    ))}
  >
    {children}
  </Composer>
);

Usage

<MultipleMutations
  mutations={[
    { mutation: MUTATION_1, variables: { foo: 'bar' }, refetchQueries: [{ query: SOME_QUERY }] }
    { mutation: MUTATION_2 },
  ]}

>
  {([ mutation1, mutation2 ]) => (
    <>
      <button disabled={mutation1.loading} onClick={() => mutation1.execute({ ... })} />
      <button disabled={mutation2.loading} onClick={() => mutation2.execute({ ... })} />
    </>
  )}
</MultipleMutations>

How to achieve the same composing result in the case of Apollo hooks?
Thanks.

Was this page helpful?
0 / 5 - 0 ratings