This may be more of a question than an issue but is it possible to throw errors in a hook and have them caught in an error boundary? Or will this not work?
What have you tried?
So basically I created a hook that wraps apollos client.. and I tried to throw from that hook. See code below: Note the commented line where i throw
import React, { useState, useContext, useEffect } from 'react';
import { ApolloClientContext } from '../../context/context';
const useQuery = ({ query, variables }) => {
const client = useContext(ApolloClientContext);
const [loading, setLoading] = useState(true);
const [data, setData] = useState();
const [error, setError] = useState();
const fetchData = async (params) => {
setLoading(true);
setError(undefined);
try {
const { data: result } = await client.query(params);
setData(result);
}
catch (e) {
// THIS IS THE IMPORTANT LINE
if( e.type === 'some type where i want to throw') {
throw e
}
setError(e);
}
setLoading(false);
};
useEffect( () => {
console.log('FETCHING DATA');
fetchData({
query,
variables
});
}, []);
return {
loading,
data,
error,
query: fetchData
};
};
export default useQuery;
I know its throwing. And I also know that I have an error boundary wrapping it.
Here is intended use case:
import React, { useState } from 'react';
import { useQuery } from '../../hooks';
const ComponentWithHook = ({
foo
}) => {
const {
loading,
data,
error
} = useQuery({
query: SOME_QUERY,
variables: { foo },
});
return (
<SomeViewComponent
loading={loading}
data={data}
error={error}
/>
);
};
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidCatch(error, errorInfo) {
this.setState({
error
});
}
render() {
return (
{error : <small>Ahh there was an error</small> : <ComponentWithHook foo="bar" />}
);
}
}
@joepuzzo
It seems to me that error boundaries do catch errors thrown inside hook https://codesandbox.io/s/24l03vz15j?fontsize=14
Mine is async... could that be the issue?
Oh wait so is yours
@joepuzzo I see.
The issue here maybe is that you are not awaiting this code inside the hook, so react moves on before it should. (please someone correct me if i'm wrong)
useEffect( () => {
console.log('FETCHING DATA');
// this should await here so useEffect awaits for the resolution of the promise
fetchData({
query,
variables
});
}, []);
I was able to replicate by not awaiting and throwing the error, which error boundary didn't catch. As soon as I await the function inside useEffect, then error boundary caught it
You're right.. im not awaiting.. however when i add that, react barfs at me saying "Warning: An Effect function must not return anything besides a function, which is used for clean-up."
So I'm assuming this is bad behavior then? And there is a better approach.
Now i don't know if error boundaries not catching errors that happens inside async function called from useEffect is by design or not. It makes sense from what I can tell
React can’t catch them — just like it wouldn’t catch errors from async class methods.
You can, however, do a trick like this:
setState(() => {
throw new Error('hi')
})
That should do what you want, both in classes and with Hooks.
hmm sneaky but I like it lol
@gaearon The combination of that approach, but using the preferred getDerivedStateFromError
instead of componentDidCatch
and rendering a child component when the error boundary is triggered causes Uncaught Invariant Violation: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
instead of rendering the fallback UI
See the modified code sandbox: https://codesandbox.io/s/2wwj41v7on
Is there something unsupported about this approach?
Swapping getDerivedStateFromError
back to componentDidCatch
fixes it, so it seems like a bug
@iwilson-r7 Just encountered the same issue. Interestingly enough, it works correctly in a production build without any hiccups. But it is certainly annoying in development as making a mistake basically throws away everything, not even dev error overlay is shown, just console message which is not helpful at all.
https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html
Error Boundaries aren't supported in React Hooks yet :(
In case anyone lands here using hooks, here's what I'm using, since we don't have setState
. If folks know a better way to propagate the error I'd love to know.
import React, { useReducer } from "react";
const initialState = {};
function reducer(state, action) {
if (action.type === 'error' && action.error) {
throw action.error;
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
memoizedCallAPI().then((response) => {
// handle good response
}).catch(error => {
dispatch({ type: 'error', error });
});
}
Actually we can just do this:
const [/* state */, setState] = useState();
setState(() => {
throw error;
})
Hi @techieshark , what do you mean by your update? Are you replacing useReducer to useState to throw errors?
@timothysantos originally i incorrectly thought I could only use the useReducer
, but later realized useState
works as well. Both seem to work, but useState
alone seemed simpler if the reducer / dispatch functionality is not also needed.
So in the end, with hooks, my full code looked like:
function Component() {
const [/* state */, setState] = useState();
useEffect(() => {
memoizedCallAPI().then((response) => {
// handle good response
}).catch(error => {
setState(() => {
throw error;
})
});
}
I hope that clarifies?
Does anyone know how to stop the error stack showing up in the UI?
@techieshark
@timothysantos
Most helpful comment
React can’t catch them — just like it wouldn’t catch errors from async class methods.
You can, however, do a trick like this:
That should do what you want, both in classes and with Hooks.