Skip to content

Commit

Permalink
Forward traced-component ref
Browse files Browse the repository at this point in the history
Cannot be merged yet, since [email protected] currently breaks on a React.forwardRef with an 'Enzyme Internal Error: unknown node with tag 14' (see issue enzymejs/enzyme#1604).
  • Loading branch information
Oblosys committed Jun 22, 2018
1 parent b5afcab commit b83bb19
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 6 deletions.
26 changes: 20 additions & 6 deletions src/traceLifecycle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types';

import * as constants from './constants';
import * as ActionCreators from './redux/actionCreators';
import { withDeprecationWarning } from './util';
import * as util from './util';
import LifecyclePanel from './components/LifecyclePanel';
import { MConstructor, MShouldUpdate, MRender, MDidMount,
MDidUpdate, MWillUnmount, MSetState, MGetDerivedState,
Expand All @@ -16,7 +16,7 @@ export const resetInstanceIdCounters = () => {
Object.keys(instanceIdCounters).forEach((k) => delete instanceIdCounters[k]);
};

export const clearInstanceIdCounters = withDeprecationWarning(
export const clearInstanceIdCounters = util.withDeprecationWarning(
constants.DEPRECATED_CLEAR_COUNTERS,
resetInstanceIdCounters
);
Expand Down Expand Up @@ -46,11 +46,11 @@ export default function traceLifecycle(ComponentToTrace) {
constructor(props, context) {
props.trace(MConstructor);
super(props, context);
this.LifecyclePanel = withDeprecationWarning(
this.LifecyclePanel = util.withDeprecationWarning(
constants.DEPRECATED_THIS_LIFECYCLE_PANEL,
props.LifecyclePanel
);
this.trace = withDeprecationWarning(
this.trace = util.withDeprecationWarning(
constants.DEPRECATED_THIS_TRACE,
props.trace
);
Expand Down Expand Up @@ -175,7 +175,15 @@ export default function traceLifecycle(ComponentToTrace) {
}

render() {
return <TracedComponent LifecyclePanel={this.LifecyclePanel} trace={this.trace} {...this.props}/>;
const { forwardedRef, ...nonRefProps } = this.props;
return (
<TracedComponent
ref={forwardedRef}
LifecyclePanel={this.LifecyclePanel}
trace={this.trace}
{...nonRefProps}
/>
);
}

// Get store directly from context, to prevent introducing extra `Connect` component.
Expand All @@ -198,5 +206,11 @@ export default function traceLifecycle(ComponentToTrace) {
delete TracedComponent.prototype.componentWillUpdate;
}

return hoistStatics(TracingComponent, ComponentToTrace);
const TracingComponentHoisted = hoistStatics(TracingComponent, ComponentToTrace);

// Prior to v16.4, React.forwardRef triggered unnecessary renders, causing infinite loops.
// (see https://github.com/facebook/react/pull/12690)
return util.reactVersionIsAtLeast(16, 4)
? React.forwardRef((props, ref) => <TracingComponentHoisted {...props} forwardedRef={ref}/>)
: TracingComponentHoisted;
}
6 changes: 6 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';
import * as constants from './constants';

export const padZeroes = (width, n) => ('' + n).padStart(width, '0');

export const reactVersionIsAtLeast = (vMajorRequired, vMinorRequired) => {
const [, vMajor, vMinor] = React.version.match(/^(\d+)\.(\d+)/);
return +vMajor > vMajorRequired || +vMajor === vMajorRequired && vMinor >= vMinorRequired;
};

export const getTimeStamp = () => {
const now = new Date();
return `[${padZeroes(2, now.getHours())}:${padZeroes(2, now.getMinutes())}:` +
Expand Down
14 changes: 14 additions & 0 deletions test/integration.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ describe('traceLifecycle', () => {
it('preserves static properties', () => {
expect(TracedChild.staticProperty).toBe('a static property');
});

// TODO: Untested because of Enzyme failure on React.forwardRef (https://github.com/airbnb/enzyme/issues/1604)
it('forwards refs', () => {
const refSpy = jest.fn();
const RefWrapper = () => (
<VisualizerProvider>
<TracedChild ref={refSpy}/>
</VisualizerProvider>
);
const refWrapper = mount(<RefWrapper/>);
const childInstance = refWrapper.find(TracedChild).childAt(0).instance();

expect(refSpy).toHaveBeenCalledWith(childInstance);
});
});

describe('LifecyclePanel', () => {
Expand Down

0 comments on commit b83bb19

Please sign in to comment.