Home > Enterprise >  React Custom Hooks - Handling errors
React Custom Hooks - Handling errors

Time:11-19

I am displaying all my api requests errors in a toast.

In my code, I have separated concepts, moving the components logic to business/ui hooks.

In order to render a toast (an imperative component), I just do the following inside a functional component:

const toast = useToast(); // UI hook

toast.display(message, { type: "error", duration: 500 });

and, in order to connect to my api, I can use custom business hooks, for example:

const useRequestSomething() {
   const [data, setData] = useState([]);
   const [isLoading, setIsLoading] = useState(false);

   const isRequesting = useRef(false);

   const requestSomething = async (someParam, onSuccess = undefined, one rror = undefined) => {
      if (isRequesting.current) return;

      isRequesting.current = true;

      setIsLoading(true);

      try {
         const data = await api.requestSomething(someParam);
         setData(data);
         onSuccess?.();
      } catch(err) {
         one rror?.(err);
      }

      setIsLoading(false);

      isRequesting.current = false;
   }

   return {
      data,
      isLoading,
      requestSomething
   }
}

My main concern is the separation of concepts... I don't think it is a good idea to use the useToast() inside the this hook that is a container of my business logic... although it may be a good idea.

So, in order to handle errors, inside any component, I can do something like:

function MyComponent() {
   const toast = useToast();

   const { t } = useTranslation(); // i18n.js hook

   const { data, isLoading, requestSomething } = useRequestSomething(); 

   const handleOnPress = () => {
      requestSomething("x", undefined, handleOnRequestSomethingError);
   }

   const handleOnRequestSomethingError = (err) => {
      toast.display(t(err), { type: "error", duration: 500 });
   }

   ... JSX
} 

It seems that I have defined some kind of callback-based api with the business hook... what do you think about my implementation?

Is it an anti-pattern to handle errors this way (with callbacks) inside hooks?

What is the typical approach to handle this situations? (I cannot use useQuery, because of my backend)

CodePudding user response:

I think your solution is good, but, IMHO, instead of prematurely handling the error, I like to let the error propagate to where we actually know how to handle it. For example, I would do this.

const requestSomething = async (params) = {
   ...
   try {
       await api.doRequest(params);
   } catch (err) {
       ... do some common clean up ...
       throw err;
   }
}
const handleOnPress = async () => {
   try {
      await requestSomething("x");
   } catch (err) {
      toast.display(t(err), { type: "error", duration: 500 });
   }
  
}

Actually, I would wrap it in a general error handler like this.

const handleOnPress = async () => {
   await withGeneralErrorHandling(async () => {
      try {
         await requestSomething("x");
      } catch (err) {
         if (err.errorCode === 'SOME_KNOWN_CASE') {
            toast.display(t(err), { type: "error", duration: 500 });
         } else {
            throw err;
         }
      }
   });
}

async function withGeneralErrorHandling(callback: () => Promise<void>) {
   try {
       await callback()
   } catch (err) {
       if (err.errorCode === 'GENERAL_CASE1') { ...}
       else if (err.errorCode === 'GENERAL_CASE2') { ... }
       else {
          if (isProduction) { reportError(err); }
          else { throw err; }
       }
   }
}

This is because I usually cannot list out all the error cases at the first implementation. Each error case will be discovered incrementally. I have to let it fail fast by letting it propagate to as closest to the outermost controller as possible.

By utilizing this built-in error propagation, you retain the stack trace information and can know exactly where the error occurs.

CodePudding user response:

Yeah, your Component knows about the Toast, every future component which handles some errors will know about the Toast.

This makes your error handling logic a little rigid, if you need to use another way of handling errors in the future, you'll have to edit every component. I'd use some state management system (redux, mobx, whatever). The idea is that in order to show an error you need to update the state of your application. Your toast component will be subscribed to the state change and react accordingly.

This way you depend on the state, not some actual component/way of displaying errors, which is more abstract and flexible.

  • Related