Skip to content

Commit

Permalink
Disable prepareStackTrace while we're generating stacks (facebook#18708)
Browse files Browse the repository at this point in the history
This could be used to do custom formatting of the stack trace in a way
that isn't compatible with how we use it. So we disable it while we use
it.

In theory we could call this ourselves with the result of our stack.
It would be a lot of extra production code though. My personal opinion
is that this should always be done server side instead of on the client.

We could expose a custom parser that converts it and passes it through
prepareStackTrace as structured data. That way it's external and doesn't
have to be built-in to React.
  • Loading branch information
sebmarkbage committed Apr 23, 2020
1 parent b9fc3d8 commit a2fb84b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
Expand Up @@ -1672,6 +1672,62 @@ describe('ReactIncrementalErrorHandling', () => {
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
});

it('provides component stack even if overriding prepareStackTrace', () => {
Error.prepareStackTrace = function(error, callsites) {
const stack = ['An error occurred:', error.message];
for (let i = 0; i < callsites.length; i++) {
const callsite = callsites[i];
stack.push(
'\t' + callsite.getFunctionName(),
'\t\tat ' + callsite.getFileName(),
'\t\ton line ' + callsite.getLineNumber(),
);
}

return stack.join('\n');
};

class ErrorBoundary extends React.Component {
state = {error: null, errorInfo: null};
componentDidCatch(error, errorInfo) {
this.setState({error, errorInfo});
}
render() {
if (this.state.errorInfo) {
Scheduler.unstable_yieldValue('render error message');
return (
<span
prop={`Caught an error:${normalizeCodeLocInfo(
this.state.errorInfo.componentStack,
)}.`}
/>
);
}
return this.props.children;
}
}

function BrokenRender(props) {
throw new Error('Hello');
}

ReactNoop.render(
<ErrorBoundary>
<BrokenRender />
</ErrorBoundary>,
);
expect(Scheduler).toFlushAndYield(['render error message']);
Error.prepareStackTrace = undefined;

expect(ReactNoop.getChildren()).toEqual([
span(
'Caught an error:\n' +
' in BrokenRender (at **)\n' +
' in ErrorBoundary (at **).',
),
]);
});

if (!ReactFeatureFlags.disableModulePatternComponents) {
it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
function Provider() {
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/ReactComponentStackFrame.js
Expand Up @@ -80,6 +80,9 @@ export function describeNativeComponentFrame(
let control;

reentry = true;
const previousPrepareStackTrace = Error.prepareStackTrace;
// $FlowFixMe It does accept undefined.
Error.prepareStackTrace = undefined;
let previousDispatcher;
if (__DEV__) {
previousDispatcher = ReactCurrentDispatcher.current;
Expand Down Expand Up @@ -184,6 +187,7 @@ export function describeNativeComponentFrame(
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
// Fallback to just using the name if we couldn't make it throw.
const name = fn ? fn.displayName || fn.name : '';
Expand Down

0 comments on commit a2fb84b

Please sign in to comment.