@samitouri / QOS-React-2 / commits / a2fb84bebd

Disable prepareStackTrace while we're generating stacks (#18708)

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.

Sebastian Markbåge committed Apr 23, 2020 at 09:16 UTC a2fb84bebdc282e026d2b16ed2825c6f564095a7
2 files changed +60
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+56
@@ -1672,6 +1672,62 @@ describe('ReactIncrementalErrorHandling', () => {
1672 expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
1673 });
1674
1675 + it('provides component stack even if overriding prepareStackTrace', () => {
1676 + Error.prepareStackTrace = function(error, callsites) {
1677 + const stack = ['An error occurred:', error.message];
1678 + for (let i = 0; i < callsites.length; i++) {
1679 + const callsite = callsites[i];
1680 + stack.push(
1681 + '\t' + callsite.getFunctionName(),
1682 + '\t\tat ' + callsite.getFileName(),
1683 + '\t\ton line ' + callsite.getLineNumber(),
1684 + );
1685 + }
1686 +
1687 + return stack.join('\n');
1688 + };
1689 +
1690 + class ErrorBoundary extends React.Component {
1691 + state = {error: null, errorInfo: null};
1692 + componentDidCatch(error, errorInfo) {
1693 + this.setState({error, errorInfo});
1694 + }
1695 + render() {
1696 + if (this.state.errorInfo) {
1697 + Scheduler.unstable_yieldValue('render error message');
1698 + return (
1699 + <span
1700 + prop={`Caught an error:${normalizeCodeLocInfo(
1701 + this.state.errorInfo.componentStack,
1702 + )}.`}
1703 + />
1704 + );
1705 + }
1706 + return this.props.children;
1707 + }
1708 + }
1709 +
1710 + function BrokenRender(props) {
1711 + throw new Error('Hello');
1712 + }
1713 +
1714 + ReactNoop.render(
1715 + <ErrorBoundary>
1716 + <BrokenRender />
1717 + </ErrorBoundary>,
1718 + );
1719 + expect(Scheduler).toFlushAndYield(['render error message']);
1720 + Error.prepareStackTrace = undefined;
1721 +
1722 + expect(ReactNoop.getChildren()).toEqual([
1723 + span(
1724 + 'Caught an error:\n' +
1725 + ' in BrokenRender (at **)\n' +
1726 + ' in ErrorBoundary (at **).',
1727 + ),
1728 + ]);
1729 + });
1730 +
1731 if (!ReactFeatureFlags.disableModulePatternComponents) {
1732 it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
1733 function Provider() {
packages/shared/ReactComponentStackFrame.js
+4
@@ -80,6 +80,9 @@ export function describeNativeComponentFrame(
80 let control;
81
82 reentry = true;
83 + const previousPrepareStackTrace = Error.prepareStackTrace;
84 + // $FlowFixMe It does accept undefined.
85 + Error.prepareStackTrace = undefined;
86 let previousDispatcher;
87 if (__DEV__) {
88 previousDispatcher = ReactCurrentDispatcher.current;
@@ -184,6 +187,7 @@ export function describeNativeComponentFrame(
187 ReactCurrentDispatcher.current = previousDispatcher;
188 reenableLogs();
189 }
190 + Error.prepareStackTrace = previousPrepareStackTrace;
191 }
192 // Fallback to just using the name if we couldn't make it throw.
193 const name = fn ? fn.displayName || fn.name : '';