How to determine mutation loading state with react-apollo graphql
Asked Answered
B

2

16

2018 Update: Apollo Client 2.1 added a new Mutation component that adds the loading property back. See @robin-wieruch's answer below and the announcement here https://dev-blog.apollodata.com/introducing-react-apollo-2-1-c837cc23d926 Read on for the original question which now only applies to earlier versions of Apollo.


Using the current version of the graphql higher-order-component in react-apollo (v0.5.2), I don't see a documented way to inform my UI that a mutation is awaiting server response. I can see that earlier versions of the package would send a property indicating loading.

Queries still receive a loading property as documented here: http://dev.apollodata.com/react/queries.html#default-result-props

My application is also using redux, so I think one way to do it is to connect my component to redux and pass down a function property that will put my UI into a loading state. Then when rewriting my graphql mutation to a property, I can make calls to update the redux store.

Something roughly like this:

function Form({ handleSubmit, loading, handleChange, value }) {
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="something"
        value={value}
        onChange={handleChange}
        disabled={loading}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Loading...' : 'Submit'}
      </button>
    </form>
  );
}

const withSubmit = graphql(
  gql`
    mutation submit($something : String) {
      submit(something : $something) {
        id
        something
      }
    }
  `, 
  {
    props: ({ ownProps, mutate }) => ({
      async handleSubmit() {
        ownProps.setLoading(true);
        try {
          const result = await mutate();
        } catch (err) {
          // @todo handle error here
        }
        ownProps.setLoading(false);
      },
    }),
  }
);

const withLoading = connect(
  (state) => ({ loading: state.loading }),
  (dispatch) => ({
    setLoading(loading) {
      dispatch(loadingAction(loading));
    },
  })
);

export default withLoading(withSubmit(Form));

I'm curious if there is a more idiomatic approach to informing the UI that the mutation is "in-flight." Thanks.

Bacchic answered 7/9, 2016 at 22:29 Comment(3)
Asking myself the exact same question (redux + apollo client: mutation loading state). Nowadays (few weeks later) I did not find any more clue about it. I still use the same approach as yours...Keynote
Mutations are called from within the component itself. What speaks against setting your custom loading state to true before running the mutation, and after the mutation has returned setting it to false again?Schiffman
Thanks, @marktani. Yeah, this was of doing it is ok. With react-apollo, queries automatically set the loading property when the request is in-flight, so I wanted to make sure I wasn't missing a built-in way to do the same thing for mutations.Bacchic
Z
3

I have re-posted this question on github and the suggested solution was to use something like a react higher order component just as you proposed in your original question. I did a similar thing – without using redux though – as outlined in this gist.

To cite Tom Coleman's response from the github issue:

It doesn't really make sense to include loading state on the mutation container; if you think about it you could call the mutation twice simultaneously -- which loading state should get passed down to the child? My feeling is in general it's not nice to mix imperative (this.mutate(x, y, z)) with declarative (props) things; it leads to irresolvable inconsistencies.

Zarate answered 17/1, 2017 at 12:22 Comment(1)
Tom's explanation about being able to send multiple mutations makes sense. And +1 for an alternate HOC solution in your gist. I'll mark this as accepted.Bacchic
S
6

Anyone who stumbles across this question, since Apollo Client 2.1 you have access to those properties in the Query and Mutation component's render props function.

import React from "react";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";

const TOGGLE_TODO = gql`
  mutation ToggleTodo($id: Int!) {
    toggleTodo(id: $id) {
      id
      completed
    }
  }
`;

const Todo = ({ id, text }) => (
  <Mutation mutation={TOGGLE_TODO} variables={{ id }}>
    {(toggleTodo, { loading, error, data }) => (
      <div>
        <p onClick={toggleTodo}>
          {text}
        </p>
        {loading && <p>Loading...</p>}
        {error && <p>Error :( Please try again</p>}
      </div>
    )}
  </Mutation>
);

Note: Example code taken from the Apollo Client 2.1 release blog post.

Selwyn answered 22/4, 2018 at 6:12 Comment(3)
Can I know the progress (%) of the loading?Funambulist
Since it is a HTTP request, you don't know the progress of it. You send a request and wait for the response to come back.Selwyn
HTTP requests can provide a progress status. I've used it before with the axios library. This is the API, note the "onprogress" event: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/uploadEzmeralda
Z
3

I have re-posted this question on github and the suggested solution was to use something like a react higher order component just as you proposed in your original question. I did a similar thing – without using redux though – as outlined in this gist.

To cite Tom Coleman's response from the github issue:

It doesn't really make sense to include loading state on the mutation container; if you think about it you could call the mutation twice simultaneously -- which loading state should get passed down to the child? My feeling is in general it's not nice to mix imperative (this.mutate(x, y, z)) with declarative (props) things; it leads to irresolvable inconsistencies.

Zarate answered 17/1, 2017 at 12:22 Comment(1)
Tom's explanation about being able to send multiple mutations makes sense. And +1 for an alternate HOC solution in your gist. I'll mark this as accepted.Bacchic

© 2022 - 2024 — McMap. All rights reserved.