mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Disable module components dynamically for WWW (#18446)
* Make disableModulePatternComponents dynamic for WWW * Run both flags and tests and respect the flag in SSR
This commit is contained in:
@@ -983,64 +983,66 @@ describe('ReactComponentLifeCycle', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls effects on module-pattern component', function() {
|
||||
const log = [];
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('calls effects on module-pattern component', function() {
|
||||
const log = [];
|
||||
|
||||
function Parent() {
|
||||
return {
|
||||
render() {
|
||||
expect(typeof this.props).toBe('object');
|
||||
log.push('render');
|
||||
return <Child />;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
log.push('will mount');
|
||||
},
|
||||
componentDidMount() {
|
||||
log.push('did mount');
|
||||
},
|
||||
componentDidUpdate() {
|
||||
log.push('did update');
|
||||
},
|
||||
getChildContext() {
|
||||
return {x: 2};
|
||||
},
|
||||
function Parent() {
|
||||
return {
|
||||
render() {
|
||||
expect(typeof this.props).toBe('object');
|
||||
log.push('render');
|
||||
return <Child />;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
log.push('will mount');
|
||||
},
|
||||
componentDidMount() {
|
||||
log.push('did mount');
|
||||
},
|
||||
componentDidUpdate() {
|
||||
log.push('did update');
|
||||
},
|
||||
getChildContext() {
|
||||
return {x: 2};
|
||||
},
|
||||
};
|
||||
}
|
||||
Parent.childContextTypes = {
|
||||
x: PropTypes.number,
|
||||
};
|
||||
function Child(props, context) {
|
||||
expect(context.x).toBe(2);
|
||||
return <div />;
|
||||
}
|
||||
Child.contextTypes = {
|
||||
x: PropTypes.number,
|
||||
};
|
||||
}
|
||||
Parent.childContextTypes = {
|
||||
x: PropTypes.number,
|
||||
};
|
||||
function Child(props, context) {
|
||||
expect(context.x).toBe(2);
|
||||
return <div />;
|
||||
}
|
||||
Child.contextTypes = {
|
||||
x: PropTypes.number,
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
|
||||
).toErrorDev(
|
||||
'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Parent to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Parent.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
|
||||
const div = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
|
||||
).toErrorDev(
|
||||
'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Parent to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Parent.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
|
||||
|
||||
expect(log).toEqual([
|
||||
'will mount',
|
||||
'render',
|
||||
'did mount',
|
||||
'ref',
|
||||
expect(log).toEqual([
|
||||
'will mount',
|
||||
'render',
|
||||
'did mount',
|
||||
'ref',
|
||||
|
||||
'render',
|
||||
'did update',
|
||||
'ref',
|
||||
]);
|
||||
});
|
||||
'render',
|
||||
'did update',
|
||||
'ref',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
it('should warn if getDerivedStateFromProps returns undefined', () => {
|
||||
class MyComponent extends React.Component {
|
||||
|
||||
@@ -108,26 +108,53 @@ describe('ReactCompositeComponent', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('should support module pattern components', () => {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('should not support module pattern components', () => {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child test="test" />, el)).toErrorDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
const el = document.createElement('div');
|
||||
expect(() => {
|
||||
expect(() => ReactDOM.render(<Child test="test" />, el)).toThrow(
|
||||
'Objects are not valid as a React child (found: object with keys {render}).',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
});
|
||||
expect(el.textContent).toBe('');
|
||||
});
|
||||
} else {
|
||||
it('should support module pattern components', () => {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child test="test" />, el)).toErrorDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
});
|
||||
}
|
||||
|
||||
it('should support rendering to different child types over time', () => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
|
||||
|
||||
@@ -459,55 +459,57 @@ describe('ReactCompositeComponent-state', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support stateful module pattern components', () => {
|
||||
function Child() {
|
||||
return {
|
||||
state: {
|
||||
count: 123,
|
||||
},
|
||||
render() {
|
||||
return <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('should support stateful module pattern components', () => {
|
||||
function Child() {
|
||||
return {
|
||||
state: {
|
||||
count: 123,
|
||||
},
|
||||
render() {
|
||||
return <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child />, el)).toErrorDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
|
||||
expect(el.textContent).toBe('count:123');
|
||||
});
|
||||
|
||||
it('should support getDerivedStateFromProps for module pattern components', () => {
|
||||
function Child() {
|
||||
return {
|
||||
state: {
|
||||
count: 1,
|
||||
},
|
||||
render() {
|
||||
return <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
Child.getDerivedStateFromProps = (props, prevState) => {
|
||||
return {
|
||||
count: prevState.count + props.incrementBy,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child />, el)).toErrorDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
const el = document.createElement('div');
|
||||
ReactDOM.render(<Child incrementBy={0} />, el);
|
||||
expect(el.textContent).toBe('count:1');
|
||||
|
||||
expect(el.textContent).toBe('count:123');
|
||||
});
|
||||
ReactDOM.render(<Child incrementBy={2} />, el);
|
||||
expect(el.textContent).toBe('count:3');
|
||||
|
||||
it('should support getDerivedStateFromProps for module pattern components', () => {
|
||||
function Child() {
|
||||
return {
|
||||
state: {
|
||||
count: 1,
|
||||
},
|
||||
render() {
|
||||
return <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
Child.getDerivedStateFromProps = (props, prevState) => {
|
||||
return {
|
||||
count: prevState.count + props.incrementBy,
|
||||
};
|
||||
};
|
||||
|
||||
const el = document.createElement('div');
|
||||
ReactDOM.render(<Child incrementBy={0} />, el);
|
||||
expect(el.textContent).toBe('count:1');
|
||||
|
||||
ReactDOM.render(<Child incrementBy={2} />, el);
|
||||
expect(el.textContent).toBe('count:3');
|
||||
|
||||
ReactDOM.render(<Child incrementBy={1} />, el);
|
||||
expect(el.textContent).toBe('count:4');
|
||||
});
|
||||
ReactDOM.render(<Child incrementBy={1} />, el);
|
||||
expect(el.textContent).toBe('count:4');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+26
-9
@@ -644,16 +644,33 @@ describe('ReactDOMServerIntegration', () => {
|
||||
checkFooDiv(await render(<ClassComponent />));
|
||||
});
|
||||
|
||||
itRenders('factory components', async render => {
|
||||
const FactoryComponent = () => {
|
||||
return {
|
||||
render: function() {
|
||||
return <div>foo</div>;
|
||||
},
|
||||
if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
itThrowsWhenRendering(
|
||||
'factory components',
|
||||
async render => {
|
||||
const FactoryComponent = () => {
|
||||
return {
|
||||
render: function() {
|
||||
return <div>foo</div>;
|
||||
},
|
||||
};
|
||||
};
|
||||
await render(<FactoryComponent />, 1);
|
||||
},
|
||||
'Objects are not valid as a React child (found: object with keys {render})',
|
||||
);
|
||||
} else {
|
||||
itRenders('factory components', async render => {
|
||||
const FactoryComponent = () => {
|
||||
return {
|
||||
render: function() {
|
||||
return <div>foo</div>;
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
checkFooDiv(await render(<FactoryComponent />, 1));
|
||||
});
|
||||
checkFooDiv(await render(<FactoryComponent />, 1));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('component hierarchies', function() {
|
||||
|
||||
@@ -782,43 +782,45 @@ describe('ReactErrorBoundaries', () => {
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
|
||||
it('renders an error state if module-style context provider throws in componentWillMount', () => {
|
||||
function BrokenComponentWillMountWithContext() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 42};
|
||||
},
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
throw new Error('Hello');
|
||||
},
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('renders an error state if module-style context provider throws in componentWillMount', () => {
|
||||
function BrokenComponentWillMountWithContext() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 42};
|
||||
},
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
throw new Error('Hello');
|
||||
},
|
||||
};
|
||||
}
|
||||
BrokenComponentWillMountWithContext.childContextTypes = {
|
||||
foo: PropTypes.number,
|
||||
};
|
||||
}
|
||||
BrokenComponentWillMountWithContext.childContextTypes = {
|
||||
foo: PropTypes.number,
|
||||
};
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
|
||||
'returns a class instance. ' +
|
||||
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
|
||||
'returns a class instance. ' +
|
||||
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
}
|
||||
|
||||
it('mounts the error message if mounting fails', () => {
|
||||
function renderError(error) {
|
||||
|
||||
@@ -814,42 +814,44 @@ describe('ReactLegacyErrorBoundaries', () => {
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
|
||||
it('renders an error state if module-style context provider throws in componentWillMount', () => {
|
||||
function BrokenComponentWillMountWithContext() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 42};
|
||||
},
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
throw new Error('Hello');
|
||||
},
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('renders an error state if module-style context provider throws in componentWillMount', () => {
|
||||
function BrokenComponentWillMountWithContext() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 42};
|
||||
},
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
throw new Error('Hello');
|
||||
},
|
||||
};
|
||||
}
|
||||
BrokenComponentWillMountWithContext.childContextTypes = {
|
||||
foo: PropTypes.number,
|
||||
};
|
||||
}
|
||||
BrokenComponentWillMountWithContext.childContextTypes = {
|
||||
foo: PropTypes.number,
|
||||
};
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
|
||||
'returns a class instance. ' +
|
||||
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
|
||||
'returns a class instance. ' +
|
||||
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
}
|
||||
|
||||
it('mounts the error message if mounting fails', () => {
|
||||
function renderError(error) {
|
||||
|
||||
+23
-21
@@ -156,29 +156,31 @@ describe('reactiverefs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('factory components', () => {
|
||||
it('Should correctly get the ref', () => {
|
||||
function Comp() {
|
||||
return {
|
||||
render() {
|
||||
return <div ref="elemRef" />;
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
describe('factory components', () => {
|
||||
it('Should correctly get the ref', () => {
|
||||
function Comp() {
|
||||
return {
|
||||
render() {
|
||||
return <div ref="elemRef" />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let inst;
|
||||
expect(
|
||||
() => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
|
||||
).toErrorDev(
|
||||
'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Comp to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Comp.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(inst.refs.elemRef.tagName).toBe('DIV');
|
||||
let inst;
|
||||
expect(
|
||||
() => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
|
||||
).toErrorDev(
|
||||
'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Comp to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Comp.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(inst.refs.elemRef.tagName).toBe('DIV');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a ref hops around children, we can track that correctly.
|
||||
|
||||
+29
-18
@@ -20,6 +20,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
disableLegacyContext,
|
||||
disableModulePatternComponents,
|
||||
enableSuspenseServerRenderer,
|
||||
enableFundamentalAPI,
|
||||
enableDeprecatedFlareAPI,
|
||||
@@ -527,28 +528,38 @@ function resolve(
|
||||
inst = Component(element.props, publicContext, updater);
|
||||
inst = finishHooks(Component, element.props, inst, publicContext);
|
||||
|
||||
if (inst == null || inst.render == null) {
|
||||
if (__DEV__) {
|
||||
// Support for module components is deprecated and is removed behind a flag.
|
||||
// Whether or not it would crash later, we want to show a good message in DEV first.
|
||||
if (inst != null && inst.render != null) {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
if (!didWarnAboutModulePatternComponent[componentName]) {
|
||||
console.error(
|
||||
'The <%s /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change %s to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
|
||||
'cannot be called with `new` by React.',
|
||||
componentName,
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutModulePatternComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the flag is on, everything is assumed to be a function component.
|
||||
// Otherwise, we also do the unfortunate dynamic checks.
|
||||
if (
|
||||
disableModulePatternComponents ||
|
||||
inst == null ||
|
||||
inst.render == null
|
||||
) {
|
||||
child = inst;
|
||||
validateRenderResult(child, Component);
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
if (!didWarnAboutModulePatternComponent[componentName]) {
|
||||
console.error(
|
||||
'The <%s /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change %s to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
|
||||
'cannot be called with `new` by React.',
|
||||
componentName,
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutModulePatternComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inst.props = element.props;
|
||||
|
||||
@@ -1374,7 +1374,35 @@ function mountIndeterminateComponent(
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
|
||||
if (__DEV__) {
|
||||
// Support for module components is deprecated and is removed behind a flag.
|
||||
// Whether or not it would crash later, we want to show a good message in DEV first.
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.render === 'function' &&
|
||||
value.$$typeof === undefined
|
||||
) {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
if (!didWarnAboutModulePatternComponent[componentName]) {
|
||||
console.error(
|
||||
'The <%s /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change %s to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
|
||||
'cannot be called with `new` by React.',
|
||||
componentName,
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutModulePatternComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
// Run these checks in production only if the flag is off.
|
||||
// Eventually we'll delete this branch altogether.
|
||||
!disableModulePatternComponents &&
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
|
||||
@@ -1237,6 +1237,7 @@ describe('ReactHooks', () => {
|
||||
'Context can only be read while React is rendering',
|
||||
);
|
||||
});
|
||||
|
||||
it('double-invokes components with Hooks in Strict Mode', () => {
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
|
||||
|
||||
@@ -1351,32 +1352,35 @@ describe('ReactHooks', () => {
|
||||
);
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1);
|
||||
|
||||
renderCount = 0;
|
||||
expect(() => renderer.update(<Factory />)).toErrorDev(
|
||||
'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Factory to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Factory.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(renderCount).toBe(1);
|
||||
renderCount = 0;
|
||||
renderer.update(<Factory />);
|
||||
expect(renderCount).toBe(1);
|
||||
renderCount = 0;
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
renderCount = 0;
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
renderCount = 0;
|
||||
expect(() => renderer.update(<Factory />)).toErrorDev(
|
||||
'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Factory to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Factory.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
expect(renderCount).toBe(1);
|
||||
renderCount = 0;
|
||||
renderer.update(<Factory />);
|
||||
expect(renderCount).toBe(1);
|
||||
|
||||
renderCount = 0;
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
renderCount = 0;
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
}
|
||||
|
||||
renderCount = 0;
|
||||
renderer.update(<HasHooks />);
|
||||
|
||||
+36
-34
@@ -187,41 +187,43 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(Scheduler).toFlushAndYield([10]);
|
||||
});
|
||||
|
||||
it('throws inside module-style components', () => {
|
||||
function Counter() {
|
||||
return {
|
||||
render() {
|
||||
const [count] = useState(0);
|
||||
return <Text text={this.props.label + ': ' + count} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
ReactNoop.render(<Counter />);
|
||||
expect(() =>
|
||||
expect(Scheduler).toFlushAndThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
|
||||
'for one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Counter to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Counter.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('throws inside module-style components', () => {
|
||||
function Counter() {
|
||||
return {
|
||||
render() {
|
||||
const [count] = useState(0);
|
||||
return <Text text={this.props.label + ': ' + count} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
ReactNoop.render(<Counter />);
|
||||
expect(() =>
|
||||
expect(Scheduler).toFlushAndThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
|
||||
'for one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
),
|
||||
).toErrorDev(
|
||||
'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Counter to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Counter.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
);
|
||||
|
||||
// Confirm that a subsequent hook works properly.
|
||||
function GoodCounter(props) {
|
||||
const [count] = useState(props.initialCount);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
ReactNoop.render(<GoodCounter initialCount={10} />);
|
||||
expect(Scheduler).toFlushAndYield([10]);
|
||||
});
|
||||
// Confirm that a subsequent hook works properly.
|
||||
function GoodCounter(props) {
|
||||
const [count] = useState(props.initialCount);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
ReactNoop.render(<GoodCounter initialCount={10} />);
|
||||
expect(Scheduler).toFlushAndYield([10]);
|
||||
});
|
||||
}
|
||||
|
||||
it('throws when called outside the render phase', () => {
|
||||
expect(() => useState(0)).toThrow(
|
||||
|
||||
@@ -2012,48 +2012,50 @@ describe('ReactIncremental', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not leak own context into context provider (factory components)', () => {
|
||||
const ops = [];
|
||||
function Recurse(props, context) {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {n: (context.n || 3) - 1};
|
||||
},
|
||||
render() {
|
||||
ops.push('Recurse ' + JSON.stringify(context));
|
||||
if (context.n === 0) {
|
||||
return null;
|
||||
}
|
||||
return <Recurse />;
|
||||
},
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('does not leak own context into context provider (factory components)', () => {
|
||||
const ops = [];
|
||||
function Recurse(props, context) {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {n: (context.n || 3) - 1};
|
||||
},
|
||||
render() {
|
||||
ops.push('Recurse ' + JSON.stringify(context));
|
||||
if (context.n === 0) {
|
||||
return null;
|
||||
}
|
||||
return <Recurse />;
|
||||
},
|
||||
};
|
||||
}
|
||||
Recurse.contextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
Recurse.childContextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
}
|
||||
Recurse.contextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
Recurse.childContextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
|
||||
ReactNoop.render(<Recurse />);
|
||||
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
|
||||
'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Recurse to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Recurse.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
'Legacy context API has been detected within a strict-mode tree.\n\n' +
|
||||
'The old API will be supported in all 16.x releases, but applications ' +
|
||||
'using it should migrate to the new version.\n\n' +
|
||||
'Please update the following components: Recurse',
|
||||
]);
|
||||
expect(ops).toEqual([
|
||||
'Recurse {}',
|
||||
'Recurse {"n":2}',
|
||||
'Recurse {"n":1}',
|
||||
'Recurse {"n":0}',
|
||||
]);
|
||||
});
|
||||
ReactNoop.render(<Recurse />);
|
||||
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
|
||||
'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Recurse to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Recurse.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
'Legacy context API has been detected within a strict-mode tree.\n\n' +
|
||||
'The old API will be supported in all 16.x releases, but applications ' +
|
||||
'using it should migrate to the new version.\n\n' +
|
||||
'Please update the following components: Recurse',
|
||||
]);
|
||||
expect(ops).toEqual([
|
||||
'Recurse {}',
|
||||
'Recurse {"n":2}',
|
||||
'Recurse {"n":1}',
|
||||
'Recurse {"n":0}',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
it('provides context when reusing work', () => {
|
||||
class Intl extends React.Component {
|
||||
|
||||
+33
-31
@@ -1695,39 +1695,41 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
|
||||
});
|
||||
|
||||
it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
|
||||
function Provider() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 'bar'};
|
||||
},
|
||||
render() {
|
||||
return 'Hi';
|
||||
},
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
|
||||
function Provider() {
|
||||
return {
|
||||
getChildContext() {
|
||||
return {foo: 'bar'};
|
||||
},
|
||||
render() {
|
||||
return 'Hi';
|
||||
},
|
||||
};
|
||||
}
|
||||
Provider.childContextTypes = {
|
||||
x: () => {},
|
||||
};
|
||||
Provider.getDerivedStateFromProps = () => {
|
||||
throw new Error('Oops!');
|
||||
};
|
||||
}
|
||||
Provider.childContextTypes = {
|
||||
x: () => {},
|
||||
};
|
||||
Provider.getDerivedStateFromProps = () => {
|
||||
throw new Error('Oops!');
|
||||
};
|
||||
|
||||
ReactNoop.render(<Provider />);
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushAndThrow('Oops!');
|
||||
}).toErrorDev([
|
||||
'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Provider to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Provider.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
'Legacy context API has been detected within a strict-mode tree.\n\n' +
|
||||
'The old API will be supported in all 16.x releases, but ' +
|
||||
'applications using it should migrate to the new version.\n\n' +
|
||||
'Please update the following components: Provider',
|
||||
]);
|
||||
});
|
||||
ReactNoop.render(<Provider />);
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushAndThrow('Oops!');
|
||||
}).toErrorDev([
|
||||
'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Provider to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Provider.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
'Legacy context API has been detected within a strict-mode tree.\n\n' +
|
||||
'The old API will be supported in all 16.x releases, but ' +
|
||||
'applications using it should migrate to the new version.\n\n' +
|
||||
'Please update the following components: Provider',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
it('uncaught errors should be discarded if the render is aborted', async () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
@@ -1379,51 +1379,53 @@ describe('ReactFreshIntegration', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('remounts deprecated factory components', () => {
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
render(`
|
||||
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
it('remounts deprecated factory components', () => {
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
render(`
|
||||
function Parent() {
|
||||
return {
|
||||
render() {
|
||||
return <Child prop="A" />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function Child({prop}) {
|
||||
return <h1>{prop}1</h1>;
|
||||
};
|
||||
|
||||
export default Parent;
|
||||
`);
|
||||
}).toErrorDev(
|
||||
'The <Parent /> component appears to be a function component ' +
|
||||
'that returns a class instance.',
|
||||
);
|
||||
const el = container.firstChild;
|
||||
expect(el.textContent).toBe('A1');
|
||||
patch(`
|
||||
function Parent() {
|
||||
return {
|
||||
render() {
|
||||
return <Child prop="A" />;
|
||||
return <Child prop="B" />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function Child({prop}) {
|
||||
return <h1>{prop}1</h1>;
|
||||
return <h1>{prop}2</h1>;
|
||||
};
|
||||
|
||||
export default Parent;
|
||||
`);
|
||||
}).toErrorDev(
|
||||
'The <Parent /> component appears to be a function component ' +
|
||||
'that returns a class instance.',
|
||||
);
|
||||
const el = container.firstChild;
|
||||
expect(el.textContent).toBe('A1');
|
||||
patch(`
|
||||
function Parent() {
|
||||
return {
|
||||
render() {
|
||||
return <Child prop="B" />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function Child({prop}) {
|
||||
return <h1>{prop}2</h1>;
|
||||
};
|
||||
|
||||
export default Parent;
|
||||
`);
|
||||
// Like classes, factory components always remount.
|
||||
expect(container.firstChild).not.toBe(el);
|
||||
const newEl = container.firstChild;
|
||||
expect(newEl.textContent).toBe('B2');
|
||||
}
|
||||
});
|
||||
// Like classes, factory components always remount.
|
||||
expect(container.firstChild).not.toBe(el);
|
||||
const newEl = container.firstChild;
|
||||
expect(newEl.textContent).toBe('B2');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('with inline requires', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -36,7 +36,7 @@ export const disableLegacyContext = __EXPERIMENTAL__;
|
||||
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
|
||||
export const enableTrustedTypesIntegration = false;
|
||||
export const disableTextareaChildren = __EXPERIMENTAL__;
|
||||
export const disableModulePatternComponents = false;
|
||||
export const disableModulePatternComponents = true;
|
||||
export const warnUnstableRenderSubtreeIntoContainer = false;
|
||||
export const deferPassiveEffectCleanupDuringUnmount = true;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = true;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// with the __VARIANT__ set to `true`, and once set to `false`.
|
||||
|
||||
export const warnAboutSpreadingKeyToJSX = __VARIANT__;
|
||||
export const disableModulePatternComponents = __VARIANT__;
|
||||
|
||||
// These are already tested in both modes using the build type dimension,
|
||||
// so we don't need to use __VARIANT__ to get extra coverage.
|
||||
|
||||
@@ -16,6 +16,7 @@ const dynamicFeatureFlags: DynamicFeatureFlags = require('ReactFeatureFlags');
|
||||
|
||||
export const {
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
disableModulePatternComponents,
|
||||
disableInputAttributeSyncing,
|
||||
enableTrustedTypesIntegration,
|
||||
warnAboutShorthandPropertyCollision,
|
||||
@@ -65,8 +66,6 @@ export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
export const disableTextareaChildren = __EXPERIMENTAL__;
|
||||
|
||||
export const disableModulePatternComponents = __EXPERIMENTAL__;
|
||||
|
||||
export const warnUnstableRenderSubtreeIntoContainer = false;
|
||||
|
||||
export const enableLegacyFBSupport = !__EXPERIMENTAL__;
|
||||
|
||||
@@ -17,8 +17,6 @@ jest.mock('shared/ReactFeatureFlags', () => {
|
||||
wwwFlags.warnAboutUnmockedScheduler = defaultFlags.warnAboutUnmockedScheduler;
|
||||
wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs;
|
||||
wwwFlags.enableDeprecatedFlareAPI = defaultFlags.enableDeprecatedFlareAPI;
|
||||
wwwFlags.disableModulePatternComponents =
|
||||
defaultFlags.disableModulePatternComponents;
|
||||
|
||||
return wwwFlags;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user