4

The Mutation component in react-apollo exposes a handy loading boolean in the render prop function which is ideal for adding loaders to the UI whilst a request is being made. In the example below my Button component calls the createPlan function when clicked which initiates a GraphQL mutation. Whilst this is happening a spinner appears on the button courtesy of the loading prop.

<Mutation mutation={CREATE_PLAN}>
  {(createPlan, { loading }) => (
    <Button
      onClick={() => createPlan({ variables: { input: {} } })}
      loading={loading}
    >
      Save
    </Button>
  )}
</Mutation>

The issue I have is that other aspects of my UI also need to change based on this loading boolean. I have tried lifting the Mutation component up the React tree so that I can manually pass the loading prop down to any components which rely on it, which works, but the page I am building has multiple mutations that can take place at any given time (such as deleting a plan, adding a single item in a plan, deleting a single item in a plan etc.) and having all of these Mutation components sitting at the page-level component feels very messy.

Is there a way that I can access the loading property outside of this Mutation component? If not, what is the best way to handle this problem? I have read that you can manually update the Apollo local state using the update function on the Mutation component (see example below) but I haven't been able to work out how to access the loading value here (plus it feels like accessing the loading property of a specific mutation without having to manually write it to the cache yourself would be a common request).

<Mutation
  mutation={CREATE_PLAN}
  update={cache => {
    cache.writeData({
      data: {
        createPlanLoading: `I DON"T HAVE ACCESS TO THE LOADING BOOLEAN HERE`,
      },
    });
  }}
>
  {(createPlan, { loading }) => (
    <Button
      onClick={() => createPlan({ variables: { input: {} } })}
      loading={loading}
    >
      Save
    </Button>
  )}
</Mutation>

2 Answers 2

1

I face the same problem in my projects and yes, putting all mutations components at the page-level component is very messy. The best way I found to handle this is by creating React states. For instance:

const [createPlanLoading, setCreatePLanLoading] = React.useState(false);

...

<Mutation mutation={CREATE_PLAN} onCompleted={() => setCreatePLanLoading(false)}>
  {(createPlan, { loading }) => (
    <Button
      onClick={() => {
        createPlan({ variables: { input: {} } });
        setCreatePLanLoading(true);
      }
      loading={loading}
    >
      Save
    </Button>
  )}
</Mutation>
Sign up to request clarification or add additional context in comments.

2 Comments

Ah OK, that makes sense (although I am not currently using hooks). How would you then change the loading state back to false after the Mutation component changes?
@GuerillaRadio Using onCompleted Mutation props: onCompleted={() => setCreatePLanLoading(false)} this way you set false when mutation completes.
0

I like the answer with React States. However, when there are many different children it looks messy with so many variables.

I've made a bit update for it for these cases:

const Parent = () => {
  const [loadingChilds, setLoading] = useState({});

  // check if at least one child item is loading, then show spinner
  const loading = Object.values(loadingChilds).reduce((t, value) => t || value, false);

  return (
  <div>
      {loading ? (
          <CircularProgress />
      ) : null}
      <Child1 setLoading={setLoading}/>
      <Child2 setLoading={setLoading}/>
  </div>
  );
};

const Child1 = ({ setLoading }) => {
  const [send, { loading }] = useMutation(MUTATION_NAME);

  useEffect(() => {
    // add info about state to the state object if it's changed
    setLoading((prev) => (prev.Child1 !== loading ? { ...prev, Child1: loading } : prev));
  });

  const someActionHandler = (variables) => {
    send({ variables});
  };



  return (
    <div>
      Child 1 Content
    </div>
  );
};


const Child2 = ({ setLoading }) => {
  const [send, { loading }] = useMutation(MUTATION_NAME2);

  useEffect(() => {
    // add info about state to the state object if it's changed
    setLoading((prev) => (prev.Child2 !== loading ? { ...prev, Child2: loading } : prev));
  });

  const someActionHandler = (variables) => {
    send({ variables});
  };



  return (
    <div>
      Child 2 Content
    </div>
  );
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.