What Is Suspense & ErrorBoundary, Exactly?
In short, Suspense
is the boundary (or fallback) for data fetching in loading
state while ErrorBoundary
is the boundary (or fallback) for data fetching in error
state.
Traditional Data Fetch
Use React-query as an example. Below is how we handle the data fetching regularly:
function Example() {
const { isLoading, error, data, isFetching } = useQuery('repoData', () =>
axios
.get('https://api.github.com/repos/tannerlinsley/react-query')
.then((res) => res.data),
);
if (isLoading) return 'Loading...';
if (error) return 'An error has occurred: ' + error.message;
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
<div>{isFetching ? 'Updating...' : ''}</div>
<ReactQueryDevtools initialIsOpen />
</div>
);
}
The drawback of this approach is:
- We need to handle all different states (
loading
,error
andloaded
) of data fetching in a single react component; - It’s difficult to propagate the data fetching states bottom-up;
Data Fetch with Suspense + ErrorBoundary
1. Enable suspense mode in React-query
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true,
},
},
});
2. Wrap the react data fetching component with Suspense + ErrorBoundary
import { ErrorBoundary } from 'react-error-boundary';
import 'bootstrap/dist/css/bootstrap.css';
import Spinner from 'react-bootstrap/Spinner';
import GithubRepositoryName from './GithubRepositoryName.react';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
const spinner = (
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
);
const REPOSITORY = 'https://api.github.com/repos/goplusgo/react-flow-template';
export default function GithubProfile(): React.MixedElement {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={spinner}>
<GithubRepositoryName repository={REPOSITORY} />
</Suspense>
</ErrorBoundary>
);
}
where in GithubRepositoryName.react.js
, we can then only focus on data successfully loaded rendering:
export default function App(): React$MixedElement {
const { data } = useQuery('repoData', () =>
axios
.get('https://api.github.com/repos/goplusgo/react-flow-template')
.then((res) => res.data),
);
return (
<div className="App">
<header className="App-header">
<p>Hello, {data.name}!</p>
</header>
</div>
);
}
Come back to the drawback #2 for traditional data fetching mechanism, we are now able to set the Suspense
or ErrorBoundary
at any higher level. For instance, if we have multiple data fetching components and we would like to display a loading spinner (or error state) as long as there is a single component remaining experiencing such state, we can do this:
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={spinner}>
<Component1 />
<Component2 />
<Component3 />
</Suspense>
</ErrorBoundary>