diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js
index 7a1f500a7aec6..a4fa8f707df0c 100644
--- a/packages/react-native-renderer/src/ReactNativeRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeRenderer.js
@@ -19,6 +19,8 @@ import ReactVersion from 'shared/ReactVersion';
// Module provided by RN:
import UIManager from 'UIManager';
+import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
+
import NativeMethodsMixin from './NativeMethodsMixin';
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
import ReactNativeComponent from './ReactNativeComponent';
@@ -35,6 +37,14 @@ injectFindHostInstance(ReactNativeFiberRenderer.findHostInstance);
ReactGenericBatching.injection.injectRenderer(ReactNativeFiberRenderer);
+function computeComponentStackForErrorReporting(reactTag: number): string {
+ let fiber = ReactNativeComponentTree.getClosestInstanceFromNode(reactTag);
+ if (!fiber) {
+ return '';
+ }
+ return getStackAddendumByWorkInProgressFiber(fiber);
+}
+
const roots = new Map();
const ReactNativeRenderer: ReactNativeType = {
@@ -99,6 +109,7 @@ const ReactNativeRenderer: ReactNativeType = {
TouchHistoryMath, // PanResponder
createReactNativeComponentClass, // RCTText, RCTView, ReactNativeART
takeSnapshot, // react-native-implementation
+ computeComponentStackForErrorReporting,
},
};
diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
new file mode 100644
index 0000000000000..4724db55f4621
--- /dev/null
+++ b/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ * @jest-environment node
+ */
+
+'use strict';
+
+let React;
+let ReactNative;
+let createReactNativeComponentClass;
+let computeComponentStackForErrorReporting;
+
+function normalizeCodeLocInfo(str) {
+ return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+}
+
+describe('ReactNativeError', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ React = require('react');
+ ReactNative = require('react-native-renderer');
+ createReactNativeComponentClass = require('../createReactNativeComponentClass')
+ .default;
+ computeComponentStackForErrorReporting =
+ ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
+ .computeComponentStackForErrorReporting;
+ });
+
+ it('should be able to extract a component stack from a native view', () => {
+ const View = createReactNativeComponentClass('View', () => ({
+ validAttributes: {foo: true},
+ uiViewClassName: 'View',
+ }));
+
+ const ref = React.createRef();
+
+ function FunctionalComponent(props) {
+ return props.children;
+ }
+
+ class ClassComponent extends React.Component {
+ render() {
+ return (
+
+
+
+ );
+ }
+ }
+
+ ReactNative.render(, 1);
+
+ let reactTag = ReactNative.findNodeHandle(ref.current);
+
+ let componentStack = normalizeCodeLocInfo(
+ computeComponentStackForErrorReporting(reactTag),
+ );
+
+ if (__DEV__) {
+ expect(componentStack).toBe(
+ '\n' +
+ ' in View (at **)\n' +
+ ' in FunctionalComponent (at **)\n' +
+ ' in ClassComponent (at **)',
+ );
+ } else {
+ expect(componentStack).toBe(
+ '\n' +
+ ' in View\n' +
+ ' in FunctionalComponent\n' +
+ ' in ClassComponent',
+ );
+ }
+ });
+});