I'm trying to catch some errors on the top level, to show the most beautiful error page in the world.
For some reason, I see my nodes disconnected, but Error Boundary never fires.
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import ApolloClient, { gql } from 'apollo-boost';
const client = new ApolloClient({ uri: "/graphql" });
const query = gql`{ items { id }}`;
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error: any) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Had error</h1>
} else {
return this.props.children;
}
}
}
function App() {
const [data, setData] = useState<any>(null);
useEffect(() => {
client.query({ query })
.then(res => setData(res))
.catch(err => { console.warn("Error:", err); throw err; });
}, []);
return <pre>Data: {data}</pre>;
}
ReactDOM.render(
<ErrorBoundary>
<App />
</ErrorBoundary>,
document.getElementById('root')
);
I run this in an empty create-react-app project.
I expect to see <h1>Had error</h1>
; I get CRA unhandled error screen.
From the docs
Error boundaries do not catch errors for:
- Event handlers (learn more)
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)
Promises are asynchronous, so rejected Promises won't be caught by error boundaries. At the moment, the recommended but somewhat hackish approach is to throw the error inside setState
. In functional components, you can use the set function returned by the useState
hook in place of setState
:
const [, setState] = useState()
useEffect(() => {
client.query({ query })
.then(res => setData(res))
.catch(err => {
console.warn("Error:", err);
setState(() => {
throw err;
});
});
}, []);
Throwing inside useEffect
would work too, but not inside a then
or catch
callback of a Promise. You also can't make the useEffect
callback an async
function because it can't return a Promise. So we're stuck with using setState
.
Also note that there is really no reason to be calling client.query
directly, particularly since this code will not rerender your UI if the cache changes. You should use useQuery
and the data
state already exposed by the hook for you.