Skip to content

Commit

Permalink
Expose component stack from reactTag to React Native renderer (#12549)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage authored Apr 5, 2018
1 parent 27535e7 commit 7a3416f
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = {
Expand Down Expand Up @@ -99,6 +109,7 @@ const ReactNativeRenderer: ReactNativeType = {
TouchHistoryMath, // PanResponder
createReactNativeComponentClass, // RCTText, RCTView, ReactNativeART
takeSnapshot, // react-native-implementation
computeComponentStackForErrorReporting,
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<FunctionalComponent>
<View foo="test" ref={ref} />
</FunctionalComponent>
);
}
}

ReactNative.render(<ClassComponent />, 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',
);
}
});
});

0 comments on commit 7a3416f

Please sign in to comment.