mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix host context issues around EventComponents and EventTargets (#15284)
This commit is contained in:
+8
-1
@@ -2112,8 +2112,15 @@ function beginWork(
|
||||
// been unsuspended it has committed as a regular Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.effectTag |= DidCapture;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventComponent:
|
||||
case EventTarget: {
|
||||
if (enableEventAPI) {
|
||||
pushHostContextForEvent(workInProgress);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
IncompleteClassComponent,
|
||||
EventComponent,
|
||||
EventTarget,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
DidCapture,
|
||||
@@ -38,6 +40,7 @@ import {
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
enableSuspenseServerRenderer,
|
||||
enableEventAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {ConcurrentMode} from './ReactTypeOfMode';
|
||||
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent';
|
||||
@@ -510,6 +513,12 @@ function unwindWork(
|
||||
case ContextProvider:
|
||||
popProvider(workInProgress);
|
||||
return null;
|
||||
case EventComponent:
|
||||
case EventTarget:
|
||||
if (enableEventAPI) {
|
||||
popHostContext(workInProgress);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ let EventComponent;
|
||||
let ReactTestRenderer;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactTestUtils;
|
||||
let EventTarget;
|
||||
let ReactEvents;
|
||||
|
||||
@@ -55,6 +56,7 @@ function initTestRenderer() {
|
||||
function initReactDOM() {
|
||||
init();
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
}
|
||||
|
||||
function initReactDOMServer() {
|
||||
@@ -219,6 +221,188 @@ describe('ReactFiberEvents', () => {
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle event components correctly with error boundaries', () => {
|
||||
function ErrorComponent() {
|
||||
throw new Error('Failed!');
|
||||
}
|
||||
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>
|
||||
<ErrorComponent />
|
||||
</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
componentDidCatch(error) {
|
||||
this.setState({
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return 'Worked!';
|
||||
}
|
||||
return <Test />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Wrapper />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput('Worked!');
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<div>
|
||||
<Child />
|
||||
</div>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
ReactNoop.render(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>
|
||||
<span>Child - 1</span>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
ReactNoop.render(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs #2', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return (
|
||||
<EventComponent>
|
||||
<div>Child</div>
|
||||
</EventComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
ReactNoop.render(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TestRenderer', () => {
|
||||
@@ -407,6 +591,190 @@ describe('ReactFiberEvents', () => {
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle event components correctly with error boundaries', () => {
|
||||
function ErrorComponent() {
|
||||
throw new Error('Failed!');
|
||||
}
|
||||
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>
|
||||
<ErrorComponent />
|
||||
</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
componentDidCatch(error) {
|
||||
this.setState({
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return 'Worked!';
|
||||
}
|
||||
return <Test />;
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Wrapper />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput('Worked!');
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<div>
|
||||
<Child />
|
||||
</div>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
|
||||
ReactTestRenderer.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>
|
||||
<span>Child - 1</span>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactTestRenderer.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
}).toWarnDev(
|
||||
'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs #2', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return (
|
||||
<EventComponent>
|
||||
<div>Child</div>
|
||||
</EventComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span>Child - 0</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactTestRenderer.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactDOM', () => {
|
||||
@@ -594,6 +962,172 @@ describe('ReactFiberEvents', () => {
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle event components correctly with error boundaries', () => {
|
||||
function ErrorComponent() {
|
||||
throw new Error('Failed!');
|
||||
}
|
||||
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>
|
||||
<ErrorComponent />
|
||||
</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
componentDidCatch(error) {
|
||||
this.setState({
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return 'Worked!';
|
||||
}
|
||||
return <Test />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Wrapper />, container);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(container.innerHTML).toBe('Worked!');
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<div>
|
||||
<Child />
|
||||
</div>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><div><span>Child - 0</span></div></div>',
|
||||
);
|
||||
|
||||
ReactTestUtils.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><div><span>Child - 1</span></div></div>',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expect(container.innerHTML).toBe('<div><span>Child - 0</span></div>');
|
||||
|
||||
expect(() => {
|
||||
ReactTestUtils.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle re-renders where there is a bail-out in a parent and an error occurs #2', () => {
|
||||
let _updateCounter;
|
||||
|
||||
function Child() {
|
||||
const [counter, updateCounter] = React.useState(0);
|
||||
|
||||
_updateCounter = updateCounter;
|
||||
|
||||
if (counter === 1) {
|
||||
return (
|
||||
<EventComponent>
|
||||
<div>Child</div>
|
||||
</EventComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Child - {counter}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Parent = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<Child />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expect(container.innerHTML).toBe('<div><span>Child - 0</span></div>');
|
||||
|
||||
expect(() => {
|
||||
ReactTestUtils.act(() => {
|
||||
_updateCounter(counter => counter + 1);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: validateDOMNesting: React event targets must not have event components as children.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactDOMServer', () => {
|
||||
|
||||
Reference in New Issue
Block a user