@samitouri / QOS-React / commits / 5890e0e692

Remove data-reactroot from server rendering and hydration heuristic (#20996)

This was used to implicitly hydrate if you call ReactDOM.render. We've had a warning to explicitly use ReactDOM.hydrate(...) instead of ReactDOM.render(...). We can now remove this from the generated markup. (And avoid adding it to Fizz.) This is a little strange to do now since we're trying hard to make the root API work the same. But if we kept it, we'd need to keep it in the generated output which adds unnecessary bytes. It also risks people relying on it, in the Fizz world where as this is an opportunity to create that clean state. We could possibly only keep it in the old server rendering APIs but then that creates an implicit dependency between which server API and which client API that you use. Currently you can really mix and match either way.

Sebastian Markbåge committed May 13, 2021 at 13:18 UTC 5890e0e692d1c39eddde0110bd0d123409f31dd3
16 files changed +31 -456
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
-39
@@ -13,7 +13,6 @@ let ChildUpdates;
13 let MorphingComponent;
14 let React;
15 let ReactDOM;
16 -let ReactDOMServer;
16 let ReactCurrentOwner;
17 let ReactTestUtils;
18 let PropTypes;
@@ -65,7 +64,6 @@ describe('ReactCompositeComponent', () => {
64 jest.resetModules();
65 React = require('react');
66 ReactDOM = require('react-dom');
68 - ReactDOMServer = require('react-dom/server');
67 ReactCurrentOwner = require('react')
68 .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
69 ReactTestUtils = require('react-dom/test-utils');
@@ -170,43 +168,6 @@ describe('ReactCompositeComponent', () => {
168 expect(el.tagName).toBe('A');
169 });
170
173 - it('should not thrash a server rendered layout with client side one', () => {
174 - class Child extends React.Component {
175 - render() {
176 - return null;
177 - }
178 - }
179 -
180 - class Parent extends React.Component {
181 - render() {
182 - return (
183 - <div>
184 - <Child />
185 - </div>
186 - );
187 - }
188 - }
189 -
190 - const markup = ReactDOMServer.renderToString(<Parent />);
191 -
192 - // Old API based on heuristic
193 - let container = document.createElement('div');
194 - container.innerHTML = markup;
195 - expect(() =>
196 - ReactDOM.render(<Parent />, container),
197 - ).toWarnDev(
198 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
199 - 'will stop working in React v18. Replace the ReactDOM.render() call ' +
200 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
201 - {withoutStack: true},
202 - );
203 -
204 - // New explicit API
205 - container = document.createElement('div');
206 - container.innerHTML = markup;
207 - ReactDOM.hydrate(<Parent />, container);
208 - });
209 -
171 it('should react to state changes from callbacks', () => {
172 const container = document.createElement('div');
173 document.body.appendChild(container);
packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js
+1 -3
@@ -139,9 +139,7 @@ describe('ReactLegacyContextDisabled', () => {
139 'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
140 'Use React.createContext() with React.useContext() instead.',
141 ]);
142 - expect(text).toBe(
143 - '<span data-reactroot="">{}<!-- -->undefined<!-- -->undefined</span>',
144 - );
142 + expect(text).toBe('<span>{}<!-- -->undefined<!-- -->undefined</span>');
143 expect(lifecycleContextLog).toEqual([{}, {}, {}]);
144 });
145
packages/react-dom/src/__tests__/ReactRenderDocument-test.js
-219
@@ -33,225 +33,6 @@ describe('rendering React components at document', () => {
33 ReactDOMServer = require('react-dom/server');
34 });
35
36 - describe('with old implicit hydration API', () => {
37 - function expectDeprecationWarningWithFiber(callback) {
38 - expect(
39 - callback,
40 - ).toWarnDev(
41 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
42 - 'will stop working in React v18. Replace the ReactDOM.render() call ' +
43 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
44 - {withoutStack: true},
45 - );
46 - }
47 -
48 - it('should be able to adopt server markup', () => {
49 - class Root extends React.Component {
50 - render() {
51 - return (
52 - <html>
53 - <head>
54 - <title>Hello World</title>
55 - </head>
56 - <body>{'Hello ' + this.props.hello}</body>
57 - </html>
58 - );
59 - }
60 - }
61 -
62 - const markup = ReactDOMServer.renderToString(<Root hello="world" />);
63 - const testDocument = getTestDocument(markup);
64 - const body = testDocument.body;
65 -
66 - expectDeprecationWarningWithFiber(() =>
67 - ReactDOM.render(<Root hello="world" />, testDocument),
68 - );
69 - expect(testDocument.body.innerHTML).toBe('Hello world');
70 -
71 - ReactDOM.render(<Root hello="moon" />, testDocument);
72 - expect(testDocument.body.innerHTML).toBe('Hello moon');
73 -
74 - expect(body === testDocument.body).toBe(true);
75 - });
76 -
77 - it('should not be able to unmount component from document node', () => {
78 - class Root extends React.Component {
79 - render() {
80 - return (
81 - <html>
82 - <head>
83 - <title>Hello World</title>
84 - </head>
85 - <body>Hello world</body>
86 - </html>
87 - );
88 - }
89 - }
90 -
91 - const markup = ReactDOMServer.renderToString(<Root />);
92 - const testDocument = getTestDocument(markup);
93 - expectDeprecationWarningWithFiber(() =>
94 - ReactDOM.render(<Root />, testDocument),
95 - );
96 - expect(testDocument.body.innerHTML).toBe('Hello world');
97 -
98 - // In Fiber this actually works. It might not be a good idea though.
99 - ReactDOM.unmountComponentAtNode(testDocument);
100 - expect(testDocument.firstChild).toBe(null);
101 - });
102 -
103 - it('should not be able to switch root constructors', () => {
104 - class Component extends React.Component {
105 - render() {
106 - return (
107 - <html>
108 - <head>
109 - <title>Hello World</title>
110 - </head>
111 - <body>Hello world</body>
112 - </html>
113 - );
114 - }
115 - }
116 -
117 - class Component2 extends React.Component {
118 - render() {
119 - return (
120 - <html>
121 - <head>
122 - <title>Hello World</title>
123 - </head>
124 - <body>Goodbye world</body>
125 - </html>
126 - );
127 - }
128 - }
129 -
130 - const markup = ReactDOMServer.renderToString(<Component />);
131 - const testDocument = getTestDocument(markup);
132 -
133 - expectDeprecationWarningWithFiber(() =>
134 - ReactDOM.render(<Component />, testDocument),
135 - );
136 - expect(testDocument.body.innerHTML).toBe('Hello world');
137 -
138 - // This works but is probably a bad idea.
139 - ReactDOM.render(<Component2 />, testDocument);
140 -
141 - expect(testDocument.body.innerHTML).toBe('Goodbye world');
142 - });
143 -
144 - it('should be able to mount into document', () => {
145 - class Component extends React.Component {
146 - render() {
147 - return (
148 - <html>
149 - <head>
150 - <title>Hello World</title>
151 - </head>
152 - <body>{this.props.text}</body>
153 - </html>
154 - );
155 - }
156 - }
157 -
158 - const markup = ReactDOMServer.renderToString(
159 - <Component text="Hello world" />,
160 - );
161 - const testDocument = getTestDocument(markup);
162 -
163 - expectDeprecationWarningWithFiber(() =>
164 - ReactDOM.render(<Component text="Hello world" />, testDocument),
165 - );
166 -
167 - expect(testDocument.body.innerHTML).toBe('Hello world');
168 - });
169 -
170 - it('renders over an existing text child without throwing', () => {
171 - const container = document.createElement('div');
172 - container.textContent = 'potato';
173 - ReactDOM.render(<div>parsnip</div>, container);
174 - expect(container.textContent).toBe('parsnip');
175 - // We don't expect a warning about new hydration API here because
176 - // we aren't sure if the user meant to hydrate or replace a stub node.
177 - // We would see a warning if the container had React-rendered HTML in it.
178 - });
179 -
180 - it('should give helpful errors on state desync', () => {
181 - class Component extends React.Component {
182 - render() {
183 - return (
184 - <html>
185 - <head>
186 - <title>Hello World</title>
187 - </head>
188 - <body>{this.props.text}</body>
189 - </html>
190 - );
191 - }
192 - }
193 -
194 - const markup = ReactDOMServer.renderToString(
195 - <Component text="Goodbye world" />,
196 - );
197 - const testDocument = getTestDocument(markup);
198 -
199 - expect(() => {
200 - expect(() =>
201 - ReactDOM.render(<Component text="Hello world" />, testDocument),
202 - ).toWarnDev(
203 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
204 - 'will stop working in React v18. Replace the ReactDOM.render() call ' +
205 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
206 - {withoutStack: true},
207 - );
208 - }).toErrorDev('Warning: Text content did not match.');
209 - });
210 -
211 - it('should throw on full document render w/ no markup', () => {
212 - const testDocument = getTestDocument();
213 -
214 - class Component extends React.Component {
215 - render() {
216 - return (
217 - <html>
218 - <head>
219 - <title>Hello World</title>
220 - </head>
221 - <body>{this.props.text}</body>
222 - </html>
223 - );
224 - }
225 - }
226 -
227 - ReactDOM.render(<Component text="Hello world" />, testDocument);
228 - expect(testDocument.body.innerHTML).toBe('Hello world');
229 - // We don't expect a warning about new hydration API here because
230 - // we aren't sure if the user meant to hydrate or replace the document.
231 - // We would see a warning if the document had React-rendered HTML in it.
232 - });
233 -
234 - it('supports findDOMNode on full-page components', () => {
235 - const tree = (
236 - <html>
237 - <head>
238 - <title>Hello World</title>
239 - </head>
240 - <body>Hello world</body>
241 - </html>
242 - );
243 -
244 - const markup = ReactDOMServer.renderToString(tree);
245 - const testDocument = getTestDocument(markup);
246 - let component;
247 - expectDeprecationWarningWithFiber(() => {
248 - component = ReactDOM.render(tree, testDocument);
249 - });
250 - expect(testDocument.body.innerHTML).toBe('Hello world');
251 - expect(ReactDOM.findDOMNode(component).tagName).toBe('HTML');
252 - });
253 - });
254 -
36 describe('with new explicit hydration API', () => {
37 it('should be able to adopt server markup', () => {
38 class Root extends React.Component {
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+5 -17
@@ -31,14 +31,12 @@ describe('ReactDOMServer', () => {
31 describe('renderToString', () => {
32 it('should generate simple markup', () => {
33 const response = ReactDOMServer.renderToString(<span>hello world</span>);
34 - expect(response).toMatch(
35 - new RegExp('<span data-reactroot=""' + '>hello world</span>'),
36 - );
34 + expect(response).toMatch(new RegExp('<span' + '>hello world</span>'));
35 });
36
37 it('should generate simple markup for self-closing tags', () => {
38 const response = ReactDOMServer.renderToString(<img />);
41 - expect(response).toMatch(new RegExp('<img data-reactroot=""' + '/>'));
39 + expect(response).toMatch(new RegExp('<img' + '/>'));
40 });
41
42 it('should generate comment markup for component returns null', () => {
@@ -74,10 +72,7 @@ describe('ReactDOMServer', () => {
72 const response = ReactDOMServer.renderToString(<Parent />);
73 expect(response).toMatch(
74 new RegExp(
77 - '<div ' +
78 - 'data-reactroot' +
79 - '=""' +
80 - '>' +
75 + '<div>' +
76 '<span' +
77 '>' +
78 'My name is <!-- -->child' +
@@ -136,12 +131,7 @@ describe('ReactDOMServer', () => {
131
132 expect(response).toMatch(
133 new RegExp(
139 - '<span ' +
140 - 'data-reactroot' +
141 - '=""' +
142 - '>' +
143 - 'Component name: <!-- -->TestComponent' +
144 - '</span>',
134 + '<span>' + 'Component name: <!-- -->TestComponent' + '</span>',
135 ),
136 );
137 expect(lifecycle).toEqual([
@@ -580,9 +570,7 @@ describe('ReactDOMServer', () => {
570 it('should generate simple markup', () => {
571 const SuccessfulElement = React.createElement(() => <img />);
572 const response = ReactDOMServer.renderToNodeStream(SuccessfulElement);
583 - expect(response.read().toString()).toMatch(
584 - new RegExp('<img data-reactroot=""' + '/>'),
585 - );
573 + expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
574 });
575
576 it('should handle errors correctly', () => {
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+2 -95
@@ -25,99 +25,6 @@ describe('ReactDOMServerHydration', () => {
25 Scheduler = require('scheduler');
26 });
27
28 - it('should have the correct mounting behavior (old hydrate API)', () => {
29 - let mountCount = 0;
30 - let numClicks = 0;
31 -
32 - class TestComponent extends React.Component {
33 - componentDidMount() {
34 - mountCount++;
35 - }
36 -
37 - click = () => {
38 - numClicks++;
39 - };
40 -
41 - render() {
42 - return (
43 - <span ref="span" onClick={this.click}>
44 - Name: {this.props.name}
45 - </span>
46 - );
47 - }
48 - }
49 -
50 - const element = document.createElement('div');
51 - document.body.appendChild(element);
52 - try {
53 - ReactDOM.render(<TestComponent />, element);
54 -
55 - let lastMarkup = element.innerHTML;
56 -
57 - // Exercise the update path. Markup should not change,
58 - // but some lifecycle methods should be run again.
59 - ReactDOM.render(<TestComponent name="x" />, element);
60 - expect(mountCount).toEqual(1);
61 -
62 - // Unmount and remount. We should get another mount event and
63 - // we should get different markup, as the IDs are unique each time.
64 - ReactDOM.unmountComponentAtNode(element);
65 - expect(element.innerHTML).toEqual('');
66 - ReactDOM.render(<TestComponent name="x" />, element);
67 - expect(mountCount).toEqual(2);
68 - expect(element.innerHTML).not.toEqual(lastMarkup);
69 -
70 - // Now kill the node and render it on top of server-rendered markup, as if
71 - // we used server rendering. We should mount again, but the markup should
72 - // be unchanged. We will append a sentinel at the end of innerHTML to be
73 - // sure that innerHTML was not changed.
74 - ReactDOM.unmountComponentAtNode(element);
75 - expect(element.innerHTML).toEqual('');
76 -
77 - lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
78 - element.innerHTML = lastMarkup;
79 -
80 - let instance;
81 -
82 - expect(() => {
83 - instance = ReactDOM.render(<TestComponent name="x" />, element);
84 - }).toWarnDev(
85 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
86 - 'will stop working in React v18. Replace the ReactDOM.render() call ' +
87 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
88 - {withoutStack: true},
89 - );
90 - expect(mountCount).toEqual(3);
91 - expect(element.innerHTML).toBe(lastMarkup);
92 -
93 - // Ensure the events system works after mount into server markup
94 - expect(numClicks).toEqual(0);
95 -
96 - instance.refs.span.click();
97 - expect(numClicks).toEqual(1);
98 -
99 - ReactDOM.unmountComponentAtNode(element);
100 - expect(element.innerHTML).toEqual('');
101 -
102 - // Now simulate a situation where the app is not idempotent. React should
103 - // warn but do the right thing.
104 - element.innerHTML = lastMarkup;
105 - expect(() => {
106 - instance = ReactDOM.render(<TestComponent name="y" />, element);
107 - }).toErrorDev('Text content did not match. Server: "x" Client: "y"');
108 - expect(mountCount).toEqual(4);
109 - expect(element.innerHTML.length > 0).toBe(true);
110 - expect(element.innerHTML).not.toEqual(lastMarkup);
111 -
112 - // Ensure the events system works after markup mismatch.
113 - expect(numClicks).toEqual(1);
114 - instance.refs.span.click();
115 - expect(numClicks).toEqual(2);
116 - } finally {
117 - document.body.removeChild(element);
118 - }
119 - });
120 -
28 it('should have the correct mounting behavior (new hydrate API)', () => {
29 let mountCount = 0;
30 let numClicks = 0;
@@ -289,7 +196,7 @@ describe('ReactDOMServerHydration', () => {
196 // Simulate IE normalizing the style attribute. IE makes it equal to
197 // what's available under `node.style.cssText`.
198 element.innerHTML =
292 - '<div style="height: 10px; color: black; text-decoration: none;" data-reactroot=""></div>';
199 + '<div style="height: 10px; color: black; text-decoration: none;"></div>';
200
201 // We don't expect to see false positive warnings.
202 // https://github.com/facebook/react/issues/11807
@@ -308,7 +215,7 @@ describe('ReactDOMServerHydration', () => {
215 const element = document.createElement('div');
216
217 element.innerHTML =
311 - '<div style="text-decoration: none; color: black; height: 10px;" data-reactroot=""></div>';
218 + '<div style="text-decoration: none; color: black; height: 10px;"></div>';
219
220 expect(() =>
221 ReactDOM.hydrate(
packages/react-dom/src/__tests__/escapeTextForBrowser-test.js
+8 -8
@@ -21,37 +21,37 @@ describe('escapeTextForBrowser', () => {
21
22 it('ampersand is escaped when passed as text content', () => {
23 const response = ReactDOMServer.renderToString(<span>{'&'}</span>);
24 - expect(response).toMatch('<span data-reactroot="">&amp;</span>');
24 + expect(response).toMatch('<span>&amp;</span>');
25 });
26
27 it('double quote is escaped when passed as text content', () => {
28 const response = ReactDOMServer.renderToString(<span>{'"'}</span>);
29 - expect(response).toMatch('<span data-reactroot="">&quot;</span>');
29 + expect(response).toMatch('<span>&quot;</span>');
30 });
31
32 it('single quote is escaped when passed as text content', () => {
33 const response = ReactDOMServer.renderToString(<span>{"'"}</span>);
34 - expect(response).toMatch('<span data-reactroot="">&#x27;</span>');
34 + expect(response).toMatch('<span>&#x27;</span>');
35 });
36
37 it('greater than entity is escaped when passed as text content', () => {
38 const response = ReactDOMServer.renderToString(<span>{'>'}</span>);
39 - expect(response).toMatch('<span data-reactroot="">&gt;</span>');
39 + expect(response).toMatch('<span>&gt;</span>');
40 });
41
42 it('lower than entity is escaped when passed as text content', () => {
43 const response = ReactDOMServer.renderToString(<span>{'<'}</span>);
44 - expect(response).toMatch('<span data-reactroot="">&lt;</span>');
44 + expect(response).toMatch('<span>&lt;</span>');
45 });
46
47 it('number is correctly passed as text content', () => {
48 const response = ReactDOMServer.renderToString(<span>{42}</span>);
49 - expect(response).toMatch('<span data-reactroot="">42</span>');
49 + expect(response).toMatch('<span>42</span>');
50 });
51
52 it('number is escaped to string when passed as text content', () => {
53 const response = ReactDOMServer.renderToString(<img data-attr={42} />);
54 - expect(response).toMatch('<img data-attr="42" data-reactroot=""/>');
54 + expect(response).toMatch('<img data-attr="42"/>');
55 });
56
57 it('escape text content representing a script tag', () => {
@@ -59,7 +59,7 @@ describe('escapeTextForBrowser', () => {
59 <span>{'<script type=\'\' src=""></script>'}</span>,
60 );
61 expect(response).toMatch(
62 - '<span data-reactroot="">&lt;script type=&#x27;&#x27; ' +
62 + '<span>&lt;script type=&#x27;&#x27; ' +
63 'src=&quot;&quot;&gt;&lt;/script&gt;</span>',
64 );
65 });
packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js
+8 -9
@@ -21,32 +21,32 @@ describe('quoteAttributeValueForBrowser', () => {
21
22 it('ampersand is escaped inside attributes', () => {
23 const response = ReactDOMServer.renderToString(<img data-attr="&" />);
24 - expect(response).toMatch('<img data-attr="&amp;" data-reactroot=""/>');
24 + expect(response).toMatch('<img data-attr="&amp;"/>');
25 });
26
27 it('double quote is escaped inside attributes', () => {
28 const response = ReactDOMServer.renderToString(<img data-attr={'"'} />);
29 - expect(response).toMatch('<img data-attr="&quot;" data-reactroot=""/>');
29 + expect(response).toMatch('<img data-attr="&quot;"/>');
30 });
31
32 it('single quote is escaped inside attributes', () => {
33 const response = ReactDOMServer.renderToString(<img data-attr="'" />);
34 - expect(response).toMatch('<img data-attr="&#x27;" data-reactroot=""/>');
34 + expect(response).toMatch('<img data-attr="&#x27;"/>');
35 });
36
37 it('greater than entity is escaped inside attributes', () => {
38 const response = ReactDOMServer.renderToString(<img data-attr=">" />);
39 - expect(response).toMatch('<img data-attr="&gt;" data-reactroot=""/>');
39 + expect(response).toMatch('<img data-attr="&gt;"/>');
40 });
41
42 it('lower than entity is escaped inside attributes', () => {
43 const response = ReactDOMServer.renderToString(<img data-attr="<" />);
44 - expect(response).toMatch('<img data-attr="&lt;" data-reactroot=""/>');
44 + expect(response).toMatch('<img data-attr="&lt;"/>');
45 });
46
47 it('number is escaped to string inside attributes', () => {
48 const response = ReactDOMServer.renderToString(<img data-attr={42} />);
49 - expect(response).toMatch('<img data-attr="42" data-reactroot=""/>');
49 + expect(response).toMatch('<img data-attr="42"/>');
50 });
51
52 it('object is passed to a string inside attributes', () => {
@@ -59,7 +59,7 @@ describe('quoteAttributeValueForBrowser', () => {
59 const response = ReactDOMServer.renderToString(
60 <img data-attr={sampleObject} />,
61 );
62 - expect(response).toMatch('<img data-attr="ponys" data-reactroot=""/>');
62 + expect(response).toMatch('<img data-attr="ponys"/>');
63 });
64
65 it('script tag is escaped inside attributes', () => {
@@ -68,8 +68,7 @@ describe('quoteAttributeValueForBrowser', () => {
68 );
69 expect(response).toMatch(
70 '<img data-attr="&lt;script type=&#x27;&#x27; ' +
71 - 'src=&quot;&quot;&gt;&lt;/script&gt;" ' +
72 - 'data-reactroot=""/>',
71 + 'src=&quot;&quot;&gt;&lt;/script&gt;"/>',
72 );
73 });
74 });
packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+1 -1
@@ -211,7 +211,7 @@ module.exports = function(initModules) {
211 element,
212 shouldUseDocument(element)
213 ? '<html><body><div id="badIdWhichWillCauseMismatch" /></body></html>'
214 - : '<div id="badIdWhichWillCauseMismatch" data-reactroot="" data-reactid="1"></div>',
214 + : '<div id="badIdWhichWillCauseMismatch"></div>',
215 );
216
217 await renderIntoDom(element, container, true, errorCount + 1);
packages/react-dom/src/client/ReactDOMComponent.js
-3
@@ -941,9 +941,6 @@ export function diffHydratedProperties(
941 for (let i = 0; i < attributes.length; i++) {
942 const name = attributes[i].name.toLowerCase();
943 switch (name) {
944 - // Built-in SSR attribute is allowed
945 - case 'data-reactroot':
946 - break;
944 // Controlled attributes are not validated
945 // TODO: Only ignore them on controlled tags.
946 case 'value':
packages/react-dom/src/client/ReactDOMLegacy.js
+2 -40
@@ -18,7 +18,6 @@ import {
18 unmarkContainerAsRoot,
19 } from './ReactDOMComponentTree';
20 import {createLegacyRoot, isValidContainer} from './ReactDOMRoot';
21 -import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
21 import {
22 DOCUMENT_NODE,
23 ELEMENT_NODE,
@@ -41,7 +40,6 @@ import {has as hasInstance} from 'shared/ReactInstanceMap';
40 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
41
42 let topLevelUpdateWarnings;
44 -let warnedAboutHydrateAPI = false;
43
44 if (__DEV__) {
45 topLevelUpdateWarnings = (container: Container) => {
@@ -102,57 +100,21 @@ function getReactRootElementInContainer(container: any) {
100 }
101 }
102
105 -function shouldHydrateDueToLegacyHeuristic(container) {
106 - const rootElement = getReactRootElementInContainer(container);
107 - return !!(
108 - rootElement &&
109 - rootElement.nodeType === ELEMENT_NODE &&
110 - rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
111 - );
112 -}
113 -
103 function legacyCreateRootFromDOMContainer(
104 container: Container,
105 forceHydrate: boolean,
106 ): RootType {
118 - const shouldHydrate =
119 - forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
107 // First clear any existing content.
121 - if (!shouldHydrate) {
122 - let warned = false;
108 + if (!forceHydrate) {
109 let rootSibling;
110 while ((rootSibling = container.lastChild)) {
125 - if (__DEV__) {
126 - if (
127 - !warned &&
128 - rootSibling.nodeType === ELEMENT_NODE &&
129 - (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
130 - ) {
131 - warned = true;
132 - console.error(
133 - 'render(): Target node has markup rendered by React, but there ' +
134 - 'are unrelated nodes as well. This is most commonly caused by ' +
135 - 'white-space inserted around server-rendered markup.',
136 - );
137 - }
138 - }
111 container.removeChild(rootSibling);
112 }
113 }
142 - if (__DEV__) {
143 - if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
144 - warnedAboutHydrateAPI = true;
145 - console.warn(
146 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
147 - 'will stop working in React v18. Replace the ReactDOM.render() call ' +
148 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
149 - );
150 - }
151 - }
114
115 return createLegacyRoot(
116 container,
155 - shouldHydrate
117 + forceHydrate
118 ? {
119 hydrate: true,
120 }
packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+2 -4
@@ -1272,7 +1272,7 @@ describe('DOMPluginEventSystem', () => {
1272 return <div ref={divRef}>Hello world</div>;
1273 }
1274 const output = ReactDOMServer.renderToString(<Test />);
1275 - expect(output).toBe(`<div data-reactroot="">Hello world</div>`);
1275 + expect(output).toBe(`<div>Hello world</div>`);
1276 });
1277
1278 // @gate experimental
@@ -1293,9 +1293,7 @@ describe('DOMPluginEventSystem', () => {
1293 );
1294 }
1295 const output = ReactDOMServer.renderToString(<Test />);
1296 - expect(output).toBe(
1297 - `<div data-reactroot=""><span>Hello world</span></div>`,
1298 - );
1296 + expect(output).toBe(`<div><span>Hello world</span></div>`);
1297 container.innerHTML = output;
1298 ReactDOM.hydrate(<Test />, container);
1299 Scheduler.unstable_flushAll();
packages/react-dom/src/server/DOMMarkupOperations.js
-5
@@ -8,7 +8,6 @@
8 */
9
10 import {
11 - ROOT_ATTRIBUTE_NAME,
11 BOOLEAN,
12 OVERLOADED_BOOLEAN,
13 getPropertyInfo,
@@ -23,10 +22,6 @@ import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser';
22 * Operations for dealing with DOM properties.
23 */
24
26 -export function createMarkupForRoot(): string {
27 - return ROOT_ATTRIBUTE_NAME + '=""';
28 -}
29 -
25 /**
26 * Creates markup for a property.
27 *
packages/react-dom/src/server/ReactPartialRenderer.js
-10
@@ -52,7 +52,6 @@ import {allocThreadID, freeThreadID} from './ReactThreadIDAllocator';
52 import {
53 createMarkupForCustomAttribute,
54 createMarkupForProperty,
55 - createMarkupForRoot,
55 } from './DOMMarkupOperations';
56 import escapeTextForBrowser from './escapeTextForBrowser';
57 import {
@@ -388,15 +387,6 @@ function createOpenTagMarkup(
387 }
388 }
389
391 - // For static pages, no need to put React ID and checksum. Saves lots of
392 - // bytes.
393 - if (makeStaticMarkup) {
394 - return ret;
395 - }
396 -
397 - if (isRootElement) {
398 - ret += ' ' + createMarkupForRoot();
399 - }
390 return ret;
391 }
392
packages/react-dom/src/shared/DOMProperty.js
-1
@@ -63,7 +63,6 @@ export const ATTRIBUTE_NAME_START_CHAR =
63 export const ATTRIBUTE_NAME_CHAR =
64 ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
65
66 -export const ROOT_ATTRIBUTE_NAME = 'data-reactroot';
66 export const VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
67 '^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$',
68 );
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+1 -1
@@ -228,7 +228,7 @@ describe('ReactScope', () => {
228 }
229 const html = ReactDOMServer.renderToString(<Test />);
230 expect(html).toBe(
231 - '<div data-reactroot=""><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
231 + '<div><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
232 );
233 container.innerHTML = html;
234 ReactDOM.hydrate(<Test />, container);
packages/react-reconciler/src/__tests__/__snapshots__/ReactHooks-test.internal.js.snap
+1 -1
@@ -1,3 +1,3 @@
1 // Jest Snapshot v1, https://goo.gl/fbAQLP
2
3 -exports[`ReactHooks works with ReactDOMServer calls inside a component 1`] = `"<p data-reactroot=\\"\\">hello</p>0<p>bye</p>"`;
3 +exports[`ReactHooks works with ReactDOMServer calls inside a component 1`] = `"<p>hello</p>0<p>bye</p>"`;