Skip to content

expose a way to override the reconciler's error handling instead of hardcoding console.error #3448

Description

@ed-sparkes

Feature request: expose a way to override the reconciler's error handling instead of hardcoding console.error

Summary

@react-pdf/reconciler wires React's root-level onUncaughtError, onCaughtError, and onRecoverableError callbacks directly to console.error, with no way for a consumer to override them. This means any render-time exception in the document tree is only ever surfaced via a raw console.error(error, errorInfo) call — it can't be routed through the app's own logger, can't be tagged with request context, and in server environments it can be silently dropped by logging pipelines that don't expect a two-argument console.error(Error, { componentStack }) call.

Current behavior

In the reconciler wrapper (e.g. @react-pdf/reconciler's reconciler-33.js, used for React 19.2+), the container is created like this:

const S = console.error;
// ...
createContainer: (containerInfo) =>
  reconciler.createContainer(
    containerInfo,
    ConcurrentRoot,
    null,
    false,
    null,
    "",
    S, // onUncaughtError
    S, // onCaughtError
    S, // onRecoverableError
    () => {},
    null
  );

All three error-handling hooks are hardcoded to console.error.

Separately, renderToBuffer/renderToStream/toBuffer (in @react-pdf/renderer) assume the root <Document> was committed successfully:

const render = async function (compress = true) {
  const props = container.document.props || {};
  // ...
};

If the tree fails to render (because a component threw and was routed to onUncaughtError), container.document is never set, and render() throws a generic, unrelated-looking:

TypeError: Cannot read properties of null (reading 'props')

Impact

When a component in the document tree throws during render (e.g. a bad prop, an unexpected null where the app's types assumed non-null), the actual error and its component stack are:

  • Only ever logged via a raw console.error call the consuming app cannot intercept, tag, or route.
  • Immediately followed by a second, unrelated TypeError from render()'s null-check, which is the only error that actually propagates up to the caller (e.g. via renderToBuffer's returned promise).

In practice this means server-side consumers only ever see the second, generic error in their logs/error tracking, with no way to get the real one — even though React already computed it and had a hook designed exactly for this purpose.

Proposed solution

Expose onUncaughtError, onCaughtError, and onRecoverableError (or a single unified onError) as options on renderToBuffer/renderToStream/renderToFile/pdf(), so consumers can plug in their own logger, e.g.:

await renderToBuffer(<MyDocument />, {
  onUncaughtError: (error, errorInfo) => {
    myLogger.error(error, { componentStack: errorInfo.componentStack });
  },
});

Even just accepting an optional onError/onRenderError callback that defaults to console.error (preserving current behavior) would unblock this.

Alternative / smaller ask

If wiring custom handlers through the public API is out of scope, even having render() check whether container.document is null and throw a clearer error (e.g. "Document failed to render — see the error logged above via console.error") would make this failure mode much easier to diagnose without a library change.

Environment

  • @react-pdf/renderer: 4.5.1
  • @react-pdf/reconciler: 2.0.0
  • React: 19.2.4
  • Node.js server-side rendering (renderToBuffer), not browser

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions