Skip to content

Commit c6f3524

Browse files
authored
Adds React event component and React event target support to SSR renderer (#15242)
* Adds React event component and React event target support to SSR renderer
1 parent c7a2dce commit c6f3524

File tree

2 files changed

+250
-1
lines changed

2 files changed

+250
-1
lines changed

packages/react-dom/src/server/ReactPartialRenderer.js

+29
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
2222
import {
2323
warnAboutDeprecatedLifecycles,
2424
enableSuspenseServerRenderer,
25+
enableEventAPI,
2526
} from 'shared/ReactFeatureFlags';
2627

2728
import {
@@ -36,6 +37,8 @@ import {
3637
REACT_CONTEXT_TYPE,
3738
REACT_LAZY_TYPE,
3839
REACT_MEMO_TYPE,
40+
REACT_EVENT_COMPONENT_TYPE,
41+
REACT_EVENT_TARGET_TYPE,
3942
} from 'shared/ReactSymbols';
4043

4144
import {
@@ -1162,6 +1165,32 @@ class ReactDOMServerRenderer {
11621165
this.stack.push(frame);
11631166
return '';
11641167
}
1168+
case REACT_EVENT_COMPONENT_TYPE:
1169+
case REACT_EVENT_TARGET_TYPE: {
1170+
if (enableEventAPI) {
1171+
const nextChildren = toArray(
1172+
((nextChild: any): ReactElement).props.children,
1173+
);
1174+
const frame: Frame = {
1175+
type: null,
1176+
domNamespace: parentNamespace,
1177+
children: nextChildren,
1178+
childIndex: 0,
1179+
context: context,
1180+
footer: '',
1181+
};
1182+
if (__DEV__) {
1183+
((frame: any): FrameDev).debugElementStack = [];
1184+
}
1185+
this.stack.push(frame);
1186+
return '';
1187+
}
1188+
invariant(
1189+
false,
1190+
'ReactDOMServer does not yet support the event API.',
1191+
);
1192+
}
1193+
// eslint-disable-next-line-no-fallthrough
11651194
case REACT_LAZY_TYPE:
11661195
invariant(
11671196
false,

packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js

+221-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @emails react-core
8-
* @jest-environment node
98
*/
109

1110
'use strict';
@@ -16,6 +15,8 @@ let Scheduler;
1615
let ReactFeatureFlags;
1716
let EventComponent;
1817
let ReactTestRenderer;
18+
let ReactDOM;
19+
let ReactDOMServer;
1920
let EventTarget;
2021
let ReactEvents;
2122

@@ -51,6 +52,16 @@ function initTestRenderer() {
5152
ReactTestRenderer = require('react-test-renderer');
5253
}
5354

55+
function initReactDOM() {
56+
init();
57+
ReactDOM = require('react-dom');
58+
}
59+
60+
function initReactDOMServer() {
61+
init();
62+
ReactDOMServer = require('react-dom/server');
63+
}
64+
5465
// This is a new feature in Fiber so I put it in its own test file. It could
5566
// probably move to one of the other test files once it is official.
5667
describe('ReactFiberEvents', () => {
@@ -358,4 +369,213 @@ describe('ReactFiberEvents', () => {
358369
);
359370
});
360371
});
372+
373+
describe('ReactDOM', () => {
374+
beforeEach(() => {
375+
initReactDOM();
376+
EventComponent = createReactEventComponent();
377+
EventTarget = ReactEvents.TouchHitTarget;
378+
});
379+
380+
it('should render a simple event component with a single child', () => {
381+
const Test = () => (
382+
<EventComponent>
383+
<div>Hello world</div>
384+
</EventComponent>
385+
);
386+
const container = document.createElement('div');
387+
ReactDOM.render(<Test />, container);
388+
expect(Scheduler).toFlushWithoutYielding();
389+
expect(container.innerHTML).toBe('<div>Hello world</div>');
390+
});
391+
392+
it('should warn when an event component has a direct text child', () => {
393+
const Test = () => <EventComponent>Hello world</EventComponent>;
394+
395+
expect(() => {
396+
const container = document.createElement('div');
397+
ReactDOM.render(<Test />, container);
398+
expect(Scheduler).toFlushWithoutYielding();
399+
}).toWarnDev(
400+
'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
401+
'Wrap the child text "Hello world" in an element.',
402+
);
403+
});
404+
405+
it('should warn when an event component has a direct text child #2', () => {
406+
const ChildWrapper = () => 'Hello world';
407+
const Test = () => (
408+
<EventComponent>
409+
<ChildWrapper />
410+
</EventComponent>
411+
);
412+
413+
expect(() => {
414+
const container = document.createElement('div');
415+
ReactDOM.render(<Test />, container);
416+
expect(Scheduler).toFlushWithoutYielding();
417+
}).toWarnDev(
418+
'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
419+
'Wrap the child text "Hello world" in an element.',
420+
);
421+
});
422+
423+
it('should render a simple event component with a single event target', () => {
424+
const Test = () => (
425+
<EventComponent>
426+
<EventTarget>
427+
<div>Hello world</div>
428+
</EventTarget>
429+
</EventComponent>
430+
);
431+
432+
const container = document.createElement('div');
433+
ReactDOM.render(<Test />, container);
434+
expect(Scheduler).toFlushWithoutYielding();
435+
expect(container.innerHTML).toBe('<div>Hello world</div>');
436+
437+
const Test2 = () => (
438+
<EventComponent>
439+
<EventTarget>
440+
<span>I am now a span</span>
441+
</EventTarget>
442+
</EventComponent>
443+
);
444+
445+
ReactDOM.render(<Test2 />, container);
446+
expect(Scheduler).toFlushWithoutYielding();
447+
expect(container.innerHTML).toBe('<span>I am now a span</span>');
448+
});
449+
450+
it('should warn when an event target has a direct text child', () => {
451+
const Test = () => (
452+
<EventComponent>
453+
<EventTarget>Hello world</EventTarget>
454+
</EventComponent>
455+
);
456+
457+
expect(() => {
458+
const container = document.createElement('div');
459+
ReactDOM.render(<Test />, container);
460+
expect(Scheduler).toFlushWithoutYielding();
461+
}).toWarnDev([
462+
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
463+
'Wrap the child text "Hello world" in an element.',
464+
'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
465+
]);
466+
});
467+
468+
it('should warn when an event target has a direct text child #2', () => {
469+
const ChildWrapper = () => 'Hello world';
470+
const Test = () => (
471+
<EventComponent>
472+
<EventTarget>
473+
<ChildWrapper />
474+
</EventTarget>
475+
</EventComponent>
476+
);
477+
478+
expect(() => {
479+
const container = document.createElement('div');
480+
ReactDOM.render(<Test />, container);
481+
expect(Scheduler).toFlushWithoutYielding();
482+
}).toWarnDev([
483+
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
484+
'Wrap the child text "Hello world" in an element.',
485+
'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
486+
]);
487+
});
488+
489+
it('should warn when an event target has more than one child', () => {
490+
const Test = () => (
491+
<EventComponent>
492+
<EventTarget>
493+
<span>Child 1</span>
494+
<span>Child 2</span>
495+
</EventTarget>
496+
</EventComponent>
497+
);
498+
499+
const container = document.createElement('div');
500+
expect(() => {
501+
ReactDOM.render(<Test />, container);
502+
expect(Scheduler).toFlushWithoutYielding();
503+
}).toWarnDev(
504+
'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
505+
);
506+
// This should not fire a warning, as this is now valid.
507+
const Test2 = () => (
508+
<EventComponent>
509+
<EventTarget>
510+
<span>Child 1</span>
511+
</EventTarget>
512+
</EventComponent>
513+
);
514+
ReactDOM.render(<Test2 />, container);
515+
expect(Scheduler).toFlushWithoutYielding();
516+
expect(container.innerHTML).toBe('<span>Child 1</span>');
517+
});
518+
519+
it('should warn if an event target is not a direct child of an event component', () => {
520+
const Test = () => (
521+
<EventComponent>
522+
<div>
523+
<EventTarget>
524+
<span>Child 1</span>
525+
</EventTarget>
526+
</div>
527+
</EventComponent>
528+
);
529+
530+
expect(() => {
531+
const container = document.createElement('div');
532+
ReactDOM.render(<Test />, container);
533+
expect(Scheduler).toFlushWithoutYielding();
534+
}).toWarnDev(
535+
'Warning: validateDOMNesting: React event targets must be direct children of event components.',
536+
);
537+
});
538+
});
539+
540+
describe('ReactDOMServer', () => {
541+
beforeEach(() => {
542+
initReactDOMServer();
543+
EventComponent = createReactEventComponent();
544+
EventTarget = ReactEvents.TouchHitTarget;
545+
});
546+
547+
it('should render a simple event component with a single child', () => {
548+
const Test = () => (
549+
<EventComponent>
550+
<div>Hello world</div>
551+
</EventComponent>
552+
);
553+
const output = ReactDOMServer.renderToString(<Test />);
554+
expect(output).toBe('<div>Hello world</div>');
555+
});
556+
557+
it('should render a simple event component with a single event target', () => {
558+
const Test = () => (
559+
<EventComponent>
560+
<EventTarget>
561+
<div>Hello world</div>
562+
</EventTarget>
563+
</EventComponent>
564+
);
565+
566+
let output = ReactDOMServer.renderToString(<Test />);
567+
expect(output).toBe('<div>Hello world</div>');
568+
569+
const Test2 = () => (
570+
<EventComponent>
571+
<EventTarget>
572+
<span>I am now a span</span>
573+
</EventTarget>
574+
</EventComponent>
575+
);
576+
577+
output = ReactDOMServer.renderToString(<Test2 />);
578+
expect(output).toBe('<span>I am now a span</span>');
579+
});
580+
});
361581
});

0 commit comments

Comments
 (0)