Mastering React Error Boundaries: Build a Flexible Error‑Handling Wheel
This tutorial walks through why catching front‑end component errors is essential, demonstrates how to implement a custom React ErrorBoundary with flexible fallback options, reset logic, higher‑order component wrappers, and hooks, and provides complete TypeScript examples and best‑practice summaries.
What Happened?
A front‑end developer receives a panic call from the boss: the page went white. After opening the console, the developer sees a response from the back‑end that contains a retcode and a data array with a valid User object, an undefined entry and a null entry. The developer initially filters out falsy values and thinks everything is fine, but later discovers that the back‑end returned an unexpected string "User not found" for some items, causing the UI to crash.
The root cause is the lack of proper type checking and error handling on the front‑end side. The correct way to handle such component exceptions in React is to use an Error Boundary .
Step 1: Copy the Official Example
Copy the example from the React documentation and output an ErrorBoundary component:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render shows the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logger.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}Wrap the business component with the boundary:
<ErrorBoundary>
<UserList />
</ErrorBoundary>When UserList throws, getDerivedStateFromError updates the state and the fallback UI is displayed instead of the broken component.
Step 2: Build a Flexible Wheel
Instead of hard‑coding the fallback UI, expose three ways to provide a fallback: fallback – a React element. FallbackComponent – a component that receives the error as a prop. fallbackRender – a render function that receives FallbackProps.
type FallbackElement = React.ReactElement | null;
interface FallbackProps { error: Error; }
interface ErrorBoundaryProps {
fallback?: FallbackElement;
FallbackComponent?: React.ComponentType<FallbackProps>;
fallbackRender?: (props: FallbackProps) => FallbackElement;
onError?: (error: Error, info: string) => void;
}
class ErrorBoundary extends React.Component<React.PropsWithChildren<ErrorBoundaryProps>, { error: Error | null }> {
static getDerivedStateFromError(error) { return { error }; }
componentDidCatch(error, errorInfo) { this.props.onError?.(error, errorInfo.componentStack); }
render() {
const { fallback, FallbackComponent, fallbackRender, children } = this.props;
const { error } = this.state;
if (error) {
if (React.isValidElement(fallback)) return fallback;
if (fallbackRender) return fallbackRender({ error });
if (FallbackComponent) return <FallbackComponent error={error} />;
throw new Error('ErrorBoundary requires one of fallback, fallbackRender, or FallbackComponent');
}
return children;
}
}Example usage with the three props:
// Using fallback element
<ErrorBoundary fallback=<div>Something went wrong</div>><UserList /></ErrorBoundary>
// Using a component
<ErrorBoundary FallbackComponent={ErrorFallback}><UserList /></ErrorBoundary>
// Using a render function
<ErrorBoundary fallbackRender={(props) => <ErrorFallback {...props} />}><UserList /></ErrorBoundary>Step 3: Add Reset Callback
Sometimes the error is temporary (e.g., a 502 response). Provide a onReset prop and expose resetErrorBoundary to the fallback so the user can retry without a full page reload.
const renderFallback = (props) => (
<div>
Something went wrong, you can <button onClick={props.resetErrorBoundary}>reset</button>.
</div>
);
<ErrorBoundary fallbackRender={renderFallback} onReset={() => console.log('reset')} onError={logger.error}>
<UserList />
</ErrorBoundary>Step 4: Listen to Render for Automatic Reset
Introduce a resetKeys array prop. When any value in the array changes, the boundary automatically resets. Also provide onResetKeysChange for custom change detection.
function changedArray(a = [], b = []) {
return a.length !== b.length || a.some((item, i) => !Object.is(item, b[i]));
}
class ErrorBoundary extends React.Component {
componentDidUpdate(prevProps) {
if (changedArray(prevProps.resetKeys, this.props.resetKeys)) {
this.props.onResetKeysChange?.(prevProps.resetKeys, this.props.resetKeys);
this.reset();
}
}
// reset() clears the error state
}To avoid resetting while the component is still rendering the error UI, a flag updatedWithError is used to differentiate the first render caused by the error from subsequent renders.
Step 5: Export a Higher‑Order Component
Wrap any component with withErrorBoundary to avoid repetitive JSX:
function withErrorBoundary(Component, errorBoundaryProps) {
const Wrapped = (props) => (
<ErrorBoundary {...errorBoundaryProps}>
<Component {...props} />
</ErrorBoundary>
);
const name = Component.displayName || Component.name || 'Component';
Wrapped.displayName = `withErrorBoundary(${name})`;
return Wrapped;
}
const UserWithErrorBoundary = withErrorBoundary(User, { onError: () => logger.error('error'), onReset: () => console.log('reset') });Step 6: Provide a Hook for Manual Error Throwing
The useErrorHandler hook lets developers throw errors from inside hooks or event handlers so that an outer ErrorBoundary can catch them.
function useErrorHandler(givenError) {
const [error, setError] = React.useState(null);
if (givenError) throw givenError;
if (error) throw error;
return setError;
}
function Greeting() {
const [greeting, setGreeting] = React.useState(null);
const handleError = useErrorHandler();
const handleSubmit = (e) => {
e.preventDefault();
const name = e.target.elements.name.value;
fetchGreeting(name).then(setGreeting, handleError);
};
return greeting ? <div>{greeting}</div> : (
<form onSubmit={handleSubmit}>
<label>Name</label>
<input id="name" />
<button type="submit">Get greeting</button>
</form>
);
}
export default withErrorBoundary(Greeting);Final Summary
Implemented a reusable ErrorBoundary component.
Used componentDidCatch and getDerivedStateFromError to capture errors and store the Error object.
Provided three ways to render fallback UI: fallback, FallbackComponent, and fallbackRender.
Added reset capabilities via onReset, resetErrorBoundary, and the resetKeys prop.
Implemented automatic reset when resetKeys changes, with optional onResetKeysChange for custom logic.
Offered a higher‑order component withErrorBoundary and a hook useErrorHandler for flexible integration.
All code is available on GitHub: https://github.com/Haixiang6123/learn-error-bounday Reference library: https://www.npmjs.com/package/react-error-boundary
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
