Try @eslint-react/kit@beta
logoESLint React

error-boundaries

Validates usage of Error Boundaries instead of try/catch for errors in child components.

Full Name in eslint-plugin-react-x

react-x/error-boundaries

Full Name in @eslint-react/eslint-plugin

@eslint-react/error-boundaries

Presets

x recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

Try/catch blocks can't catch errors that happen during React's rendering process. Errors thrown in rendering methods or lifecycle hooks bubble up through the component tree. Only Error Boundaries can catch these errors.

Similarly, the use hook doesn't throw errors in the traditional sense — it suspends component execution. When use encounters a rejected promise, only Error Boundaries (and Suspense boundaries) can handle these cases. A catch block around use would never run.

Examples

Wrapping JSX in try/catch

React's rendering is declarative. When you write return <ChildComponent />, you're describing what the UI should look like — you're not imperatively executing ChildComponent. If a child throws during rendering, React handles that error propagation internally through the component tree, bypassing any try/catch in JavaScript's call stack.

// Problem: try/catch cannot catch rendering errors in child components
function Parent() {
  try {
    return <ChildComponent />;
    //      ^^^ Use an Error Boundary to catch errors in child components.
  } catch (error) {
    return <div>Error occurred</div>;
  }
}
// Recommended: Use an Error Boundary to catch rendering errors
function Parent() {
  return (
    <ErrorBoundary fallback={<div>Error occurred</div>}>
      <ChildComponent />
    </ErrorBoundary>
  );
}

Using try/catch with the use hook

The use hook doesn't throw errors in the traditional sense — it suspends component execution. When use encounters a pending promise, it suspends the component and lets React show a fallback. When the promise rejects, React propagates the error through the component tree to the nearest Error Boundary.

// Problem: The catch block will never execute
function Component({ promise }) {
  try {
    const data = use(promise);
    //           ^^^ Use an Error Boundary instead of try/catch around the 'use' hook.
    return <div>{data}</div>;
  } catch (error) {
    return <div>Failed to load</div>;
  }
}
// Recommended: Use an Error Boundary with Suspense to handle async data
function App() {
  return (
    <ErrorBoundary fallback={<div>Failed to load</div>}>
      <Suspense fallback={<div>Loading...</div>}>
        <DataComponent promise={fetchData()} />
      </Suspense>
    </ErrorBoundary>
  );
}

Non-rendering operations

Try/catch is perfectly valid for synchronous operations like JSON.parse, localStorage access, or data transformations that happen before JSX is returned. It is also valid inside event handlers and effects. The rule only flags try/catch blocks that wrap JSX return statements or use hook calls inside component functions.

// OK: Synchronous data processing
function Component() {
  let data;
  try {
    data = JSON.parse(text);
  } catch (e) {
    data = null;
  }
  return <div>{data}</div>;
}
// OK: Error handling in event handlers
function Component() {
  const handleClick = () => {
    try {
      doSomething();
    } catch (e) {
      console.error(e);
    }
  };
  return <button onClick={handleClick}>Click</button>;
}

Troubleshooting

Why is the linter telling me not to wrap use in try/catch?

The use hook doesn't throw errors in the traditional sense — it suspends component execution. When use encounters a pending promise, it suspends the component and lets React show a fallback. Only Suspense and Error Boundaries can handle these cases. The linter warns against try/catch around use to prevent confusion as the catch block would never run.

// Problem: try/catch around `use` hook
function Component({ promise }) {
  try {
    const data = use(promise); // Won't catch — `use` suspends, not throws
    return <div>{data}</div>;
  } catch (error) {
    return <div>Failed to load</div>; // Unreachable
  }
}
// Recommended: Use an Error Boundary with Suspense
function App() {
  return (
    <ErrorBoundary fallback={<div>Failed to load</div>}>
      <Suspense fallback={<div>Loading...</div>}>
        <DataComponent promise={fetchData()} />
      </Suspense>
    </ErrorBoundary>
  );
}

Versions

Resources

Further Reading


See Also

On this page