Estoy mostrando todos mis errores de solicitud de API en un brindis .
En mi código, tengo conceptos separados, moviendo la lógica de los componentes a ganchos de negocios/ui.
Para hacer un brindis (un componente imperativo), solo hago lo siguiente dentro de un componente funcional:
const toast = useToast(); // UI hook toast.display(message, { type: "error", duration: 500 });y, para conectarme a mi api, puedo usar ganchos comerciales personalizados, por ejemplo:
const useRequestSomething() { const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(false); const isRequesting = useRef(false); const requestSomething = async (someParam, onSuccess = undefined, onError = undefined) => { if (isRequesting.current) return; isRequesting.current = true; setIsLoading(true); try { const data = await api.requestSomething(someParam); setData(data); onSuccess?.(); } catch(err) { onError?.(err); } setIsLoading(false); isRequesting.current = false; } return { data, isLoading, requestSomething } }Mi principal preocupación es la separación de conceptos... No creo que sea una buena idea usar useToast() dentro de this hook que es un contenedor de mi lógica de negocios... aunque puede ser una buena idea.
Entonces, para manejar los errores, dentro de cualquier componente, puedo hacer algo como:
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 }Parece que he definido algún tipo de API basada en devolución de llamada con el gancho comercial... ¿qué opinas de mi implementación?
¿Es un antipatrón manejar los errores de esta manera (con devoluciones de llamada) dentro de los ganchos?
¿Cuál es el enfoque típico para manejar estas situaciones? (No puedo usar useQuery, debido a mi backend)
Creo que su solución es buena, pero, en mi humilde opinión, en lugar de manejar el error prematuramente, me gusta dejar que el error se propague hasta donde realmente sabemos cómo manejarlo. Por ejemplo, yo haría esto.
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 }); } }En realidad, lo envolvería en un controlador de errores general como este.
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; } } } }Esto se debe a que normalmente no puedo enumerar todos los casos de error en la primera implementación. Cada caso de error se descubrirá de forma incremental. Tengo que dejar que falle rápido dejando que se propague lo más cerca posible del controlador más externo.
Al utilizar esta propagación de errores incorporada, conserva la información de seguimiento de la pila y puede saber exactamente dónde ocurre el error.
Sí, su Componente sabe sobre el Toast , cada componente futuro que maneje algunos errores sabrá sobre el Toast .
Esto hace que su lógica de manejo de errores sea un poco rígida, si necesita usar otra forma de manejar errores en el futuro, tendrá que editar cada componente. Usaría algún sistema de gestión de estado (redux, mobx, lo que sea). La idea es que para mostrar un error necesitas actualizar el estado de tu aplicación. Su componente de brindis se suscribirá al cambio de estado y reaccionará en consecuencia.
De esta manera, depende del estado, no de algún componente/forma real de mostrar errores, que es más abstracto y flexible.