Expose component stack from reactTag to React Native renderer (#12549)
This is not safe in general and therefore shouldn't be exposed to anything other than React Native internals. It will also need a different version in Fabric that will not have the reactTag exposed.
Sebastian Markbåge committed
Apr 4, 2018 at 17:18 UTC
7a3416f27532ac25849dfbc505300d469b43bbcc
2 files changed
+92
packages/react-native-renderer/src/ReactNativeRenderer.js
+11
@@ -19,6 +19,8 @@ import ReactVersion from 'shared/ReactVersion';
19
// Module provided by RN:
20
import UIManager from 'UIManager';
21
22
+import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
23
+
24
import NativeMethodsMixin from './NativeMethodsMixin';
25
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
26
import ReactNativeComponent from './ReactNativeComponent';
@@ -35,6 +37,14 @@ injectFindHostInstance(ReactNativeFiberRenderer.findHostInstance);
37
38
ReactGenericBatching.injection.injectRenderer(ReactNativeFiberRenderer);
39
40
+function computeComponentStackForErrorReporting(reactTag: number): string {
41
+ let fiber = ReactNativeComponentTree.getClosestInstanceFromNode(reactTag);
42
+ if (!fiber) {
43
+ return '';
44
+ }
45
+ return getStackAddendumByWorkInProgressFiber(fiber);
46
+}
47
+
48
const roots = new Map();
49
50
const ReactNativeRenderer: ReactNativeType = {
@@ -99,6 +109,7 @@ const ReactNativeRenderer: ReactNativeType = {
109
TouchHistoryMath, // PanResponder
110
createReactNativeComponentClass, // RCTText, RCTView, ReactNativeART
111
takeSnapshot, // react-native-implementation
112
+ computeComponentStackForErrorReporting,
113
},
114
};
115
packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
new
+81
@@ -0,0 +1,81 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ * @jest-environment node
9
+ */
10
+
11
+'use strict';
12
+
13
+let React;
14
+let ReactNative;
15
+let createReactNativeComponentClass;
16
+let computeComponentStackForErrorReporting;
17
+
18
+function normalizeCodeLocInfo(str) {
19
+ return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
20
+}
21
+
22
+describe('ReactNativeError', () => {
23
+ beforeEach(() => {
24
+ jest.resetModules();
25
+
26
+ React = require('react');
27
+ ReactNative = require('react-native-renderer');
28
+ createReactNativeComponentClass = require('../createReactNativeComponentClass')
29
+ .default;
30
+ computeComponentStackForErrorReporting =
31
+ ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
32
+ .computeComponentStackForErrorReporting;
33
+ });
34
+
35
+ it('should be able to extract a component stack from a native view', () => {
36
+ const View = createReactNativeComponentClass('View', () => ({
37
+ validAttributes: {foo: true},
38
+ uiViewClassName: 'View',
39
+ }));
40
+
41
+ const ref = React.createRef();
42
+
43
+ function FunctionalComponent(props) {
44
+ return props.children;
45
+ }
46
+
47
+ class ClassComponent extends React.Component {
48
+ render() {
49
+ return (
50
+ <FunctionalComponent>
51
+ <View foo="test" ref={ref} />
52
+ </FunctionalComponent>
53
+ );
54
+ }
55
+ }
56
+
57
+ ReactNative.render(<ClassComponent />, 1);
58
+
59
+ let reactTag = ReactNative.findNodeHandle(ref.current);
60
+
61
+ let componentStack = normalizeCodeLocInfo(
62
+ computeComponentStackForErrorReporting(reactTag),
63
+ );
64
+
65
+ if (__DEV__) {
66
+ expect(componentStack).toBe(
67
+ '\n' +
68
+ ' in View (at **)\n' +
69
+ ' in FunctionalComponent (at **)\n' +
70
+ ' in ClassComponent (at **)',
71
+ );
72
+ } else {
73
+ expect(componentStack).toBe(
74
+ '\n' +
75
+ ' in View\n' +
76
+ ' in FunctionalComponent\n' +
77
+ ' in ClassComponent',
78
+ );
79
+ }
80
+ });
81
+});