react-apollo-hooks icon indicating copy to clipboard operation
react-apollo-hooks copied to clipboard

onCompleted and onError not working with mutation

Open shlomokraus opened this issue 7 years ago • 14 comments

Hey, great library.

I'm trying to catch mutation errors but it seems none of the callbacks are called. The only way I was able to catch errors is to use errorPolicy: "ignore" and then checking the result in refetchQueries callback.

Is it an error or am I doing something wrong?

addUser = useMutation(mutation, {
    update: (proxy, mutationResult) => {
       /* NOT CALLED */
    },
    refetchQueries: (result) => {
      /* CALLED ON errorPolicy: "ignore" */
    },
    onCompleted: (data) => {
       /* NOT CALLED */
    },
    onError: (data) => {
       /* NOT CALLED */
    }
  });

shlomokraus avatar Nov 19 '18 07:11 shlomokraus

Hi @shlomokraus

Right now you can use only the mutation options supported directly by apollo-client. onCompleted and onError are react-apollo specific. You can have a similar result with something like that:

const addUser = useMutation(mutation);

const myHandler = () => {
  addUser().then(
    result => {
      // success callback
    },
    error => {
      // error callback
   }
  );
}

Or with async/await syntax:

const addUser = useMutation(mutation);

async function myHandler() {
  let result;
  try {
    result = await addUser();
  } catch (error) {
    // error handler
  }
}

And then:

<button onClick={myHandler}>Add user</button>

trojanowski avatar Nov 19 '18 21:11 trojanowski

Got it. So why not use the react-apollo client instead? After all the library name is react-apollo-hooks :)

shlomokraus avatar Nov 20 '18 14:11 shlomokraus

Actually, I see that the onCompleted comes from the Component of react-apollo and not from the client itself so it is not relevant

shlomokraus avatar Nov 20 '18 16:11 shlomokraus

I've opened https://github.com/apollographql/apollo-client/pull/4239

sijad avatar Dec 18 '18 06:12 sijad

I needed this and loading so I made a wrapper hook. To those who may find it useful:

import { useState } from 'react';
import { useMutation as useHookMutation } from 'react-apollo-hooks';

export function useMutation(
  mutation,
  { onCompleted, onError, ...options } = {}
) {
  const [loading, setLoading] = useState(false);
  const [called, setCalled] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  const mutate = useHookMutation(mutation, options);

  const handler = async (...args) => {
    setLoading(true);
    setCalled(true);
    setError(null);
    setData(null);

    try {
      const { data } = await mutate(...args);

      setData(data);

      setLoading(false);

      if (onCompleted) {
        onCompleted(data);
      }

      return { data };
    } catch (e) {
      setLoading(false);
      setError(e);

      if (onError) {
        onError(e);
      }
    }
  };

  return [
    handler,
    {
      loading,
      called,
      error,
      data
    }
  ];
}

zebapy avatar Jan 30 '19 21:01 zebapy

@zebapy I used your technic but it causes re-render my component many time.

zakiullahbarakzai avatar Mar 23 '19 12:03 zakiullahbarakzai

@zebapy it's not super optimized as I'm just getting the hang of hooks, so I'm open to suggestions :) or just wait until these libs have their own. I would imagine it would rerender on each state change regardless?

zebapy avatar Mar 23 '19 13:03 zebapy

yes it's rerender on each state change.

zakiullahbarakzai avatar Mar 23 '19 14:03 zakiullahbarakzai

@zakiullahbarakzai are you using useEffect hook? I've had rerendering issue as well and solved it with useEffect.

noncototient avatar Apr 05 '19 03:04 noncototient

Just in case somebody lands here I have a typescript-enhanced hook based on @zebapy's comment:

import { useState } from "react";
import { OperationVariables } from "apollo-client";
import {
  useMutation as useHookMutation,
  MutationFn,
  MutationHookOptions
} from "react-apollo-hooks";
import { DocumentNode } from "graphql";

interface CustomMutationHookOptions<TData, TVariables, TCache = object>
  extends MutationHookOptions<TData, TVariables, TCache> {
  onCompleted?(data: TData): void;
  onError?(e: Error): void;
}

interface MutationState<TData> {
  loading: boolean;
  called: boolean;
  error?: any;
  data?: TData;
}

export function useMutation<
  TData,
  TVariables = OperationVariables,
  TCache = object
>(
  mutation: DocumentNode,
  options?: CustomMutationHookOptions<TData, TVariables, TCache>
): [MutationFn<TData, TVariables>, MutationState<TData>] {
  if (options === undefined) {
    options = {};
  }
  const { onCompleted, onError, ...restOperationVars } = options;
  const [state, setState] = useState<MutationState<TData>>({
    loading: false,
    called: false
  });

  const mutate = useHookMutation<TData, TVariables, TCache>(
    mutation,
    restOperationVars
  );

  const handler: MutationFn<TData, TVariables> = async (...args) => {
    setState({
      loading: true,
      called: true
    });

    try {
      const { data } = await mutate(...args);
      setState({ ...state, loading: false, data: data });

      if (onCompleted && data !== undefined) {
        onCompleted(data);
      }

      return { data };
    } catch (e) {
      setState({ ...state, loading: false, error: e });

      if (onError) {
        onError(e);
      }

      return {};
    }
  };

  return [handler, state];
}

bowd avatar Jul 08 '19 19:07 bowd

Hi @shlomokraus

Right now you can use only the mutation options supported directly by apollo-client. onCompleted and onError are react-apollo specific. You can have a similar result with something like that:

const addUser = useMutation(mutation);

const myHandler = () => {
  addUser().then(
    result => {
      // success callback
    },
    error => {
      // error callback
   }
  );
}

Or with async/await syntax:

const addUser = useMutation(mutation);

async function myHandler() {
  let result;
  try {
    result = await addUser();
  } catch (error) {
    // error handler
  }
}

And then:

<button onClick={myHandler}>Add user</button>

Usemutation, now returns a tuple, so this will give error as addUser is not a function.

abhi40308 avatar Sep 25 '19 18:09 abhi40308

Use this instead :

const [mutateMe, { error, loading, data }] = useMutation(MUTANT)
if (error) {
    console.log(`error in mutation ${error}`)
}

See : https://github.com/trojanowski/react-apollo-hooks/pull/93

abhi40308 avatar Sep 25 '19 18:09 abhi40308

Right now you can use only the mutation options supported directly by apollo-client. onCompleted and onError are react-apollo specific.

I'm confused, from the documentation: https://www.apollographql.com/docs/react/data/mutations/#usemutation-api

The useMutation hook accepts the following options: onCompleted | (data: TData) => void | A callback executed once your mutation successfully completes

plus- avatar Oct 14 '19 18:10 plus-

@plus- that is official react apollo hook documentation, which is not this repo.

zebapy avatar Oct 14 '19 18:10 zebapy