@samitouri / QOS-React / commits / c55ffb37f1

Fix false positive hydration warning for SVG attributes (#10676)

* Fix false positive hydration warning for SVG attributes * Undo the generic fix * Pass namespace through and use it to determine sensitivity

Dan Abramov committed Sep 13, 2017 at 16:31 UTC c55ffb37f1b8972dc954b8949e7fca6e8bce6c91
8 files changed +63 -18
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+14 -2
@@ -776,6 +776,7 @@ var ReactDOMFiberComponent = {
776 domElement: Element,
777 tag: string,
778 rawProps: Object,
779 + parentNamespace: string,
780 rootContainerElement: Element | Document,
781 ): null | Array<mixed> {
782 if (__DEV__) {
@@ -912,6 +913,8 @@ var ReactDOMFiberComponent = {
913 case 'selected':
914 break;
915 default:
916 + // Intentionally use the original name.
917 + // See discussion in https://github.com/facebook/react/pull/10676.
918 extraAttributeNames.add(attributes[i].name);
919 }
920 }
@@ -1007,8 +1010,17 @@ var ReactDOMFiberComponent = {
1010 nextProp,
1011 );
1012 } else {
1010 - // $FlowFixMe - Should be inferred as not undefined.
1011 - extraAttributeNames.delete(propKey.toLowerCase());
1013 + let ownNamespace = parentNamespace;
1014 + if (ownNamespace === HTML_NAMESPACE) {
1015 + ownNamespace = getIntrinsicNamespace(tag);
1016 + }
1017 + if (ownNamespace === HTML_NAMESPACE) {
1018 + // $FlowFixMe - Should be inferred as not undefined.
1019 + extraAttributeNames.delete(propKey.toLowerCase());
1020 + } else {
1021 + // $FlowFixMe - Should be inferred as not undefined.
1022 + extraAttributeNames.delete(propKey);
1023 + }
1024 serverValue = DOMPropertyOperations.getValueForAttribute(
1025 domElement,
1026 propKey,
src/renderers/dom/fiber/ReactDOMFiberEntry.js
+15 -1
@@ -488,13 +488,27 @@ var DOMRenderer = ReactFiberReconciler({
488 type: string,
489 props: Props,
490 rootContainerInstance: Container,
491 + hostContext: HostContext,
492 internalInstanceHandle: Object,
493 ): null | Array<mixed> {
494 precacheFiberNode(internalInstanceHandle, instance);
495 // TODO: Possibly defer this until the commit phase where all the events
496 // get attached.
497 updateFiberProps(instance, props);
497 - return diffHydratedProperties(instance, type, props, rootContainerInstance);
498 + let parentNamespace: string;
499 + if (__DEV__) {
500 + const hostContextDev = ((hostContext: any): HostContextDev);
501 + parentNamespace = hostContextDev.namespace;
502 + } else {
503 + parentNamespace = ((hostContext: any): HostContextProd);
504 + }
505 + return diffHydratedProperties(
506 + instance,
507 + type,
508 + props,
509 + parentNamespace,
510 + rootContainerInstance,
511 + );
512 },
513
514 hydrateTextInstance(
src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+20 -9
@@ -1325,19 +1325,30 @@ describe('ReactDOMServerIntegration', () => {
1325 expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
1326 });
1327
1328 - itRenders('svg child element', async render => {
1329 - let e = await render(
1330 - <svg><image xlinkHref="http://i.imgur.com/w7GCRPb.png" /></svg>,
1331 - );
1332 - e = e.firstChild;
1328 + itRenders('svg child element with an attribute', async render => {
1329 + let e = await render(<svg viewBox="0 0 0 0" />);
1330 expect(e.childNodes.length).toBe(0);
1334 - expect(e.tagName).toBe('image');
1331 + expect(e.tagName).toBe('svg');
1332 expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
1336 - expect(e.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe(
1337 - 'http://i.imgur.com/w7GCRPb.png',
1338 - );
1333 + expect(e.getAttribute('viewBox')).toBe('0 0 0 0');
1334 });
1335
1336 + itRenders(
1337 + 'svg child element with a namespace attribute',
1338 + async render => {
1339 + let e = await render(
1340 + <svg><image xlinkHref="http://i.imgur.com/w7GCRPb.png" /></svg>,
1341 + );
1342 + e = e.firstChild;
1343 + expect(e.childNodes.length).toBe(0);
1344 + expect(e.tagName).toBe('image');
1345 + expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
1346 + expect(e.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe(
1347 + 'http://i.imgur.com/w7GCRPb.png',
1348 + );
1349 + },
1350 + );
1351 +
1352 itRenders('svg child element with a badly cased alias', async render => {
1353 let e = await render(
1354 <svg><image xlinkhref="http://i.imgur.com/w7GCRPb.png" /></svg>,
src/renderers/shared/fiber/ReactFiberBeginWork.js
+1 -1
@@ -72,7 +72,7 @@ if (__DEV__) {
72 module.exports = function<T, P, I, TI, PI, C, CX, PL>(
73 config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
74 hostContext: HostContext<C, CX>,
75 - hydrationContext: HydrationContext<C>,
75 + hydrationContext: HydrationContext<C, CX>,
76 scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
77 getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
78 ) {
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2 -1
@@ -53,7 +53,7 @@ var invariant = require('fbjs/lib/invariant');
53 module.exports = function<T, P, I, TI, PI, C, CX, PL>(
54 config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
55 hostContext: HostContext<C, CX>,
56 - hydrationContext: HydrationContext<C>,
56 + hydrationContext: HydrationContext<C, CX>,
57 ) {
58 const {
59 createInstance,
@@ -289,6 +289,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
289 prepareToHydrateHostInstance(
290 workInProgress,
291 rootContainerInstance,
292 + currentHostContext,
293 )
294 ) {
295 // If changes to the hydrated node needs to be applied at the
src/renderers/shared/fiber/ReactFiberHydrationContext.js
+9 -3
@@ -22,18 +22,22 @@ const {Deletion, Placement} = require('ReactTypeOfSideEffect');
22
23 const {createFiberFromHostInstanceForDeletion} = require('ReactFiber');
24
25 -export type HydrationContext<C> = {
25 +export type HydrationContext<C, CX> = {
26 enterHydrationState(fiber: Fiber): boolean,
27 resetHydrationState(): void,
28 tryToClaimNextHydratableInstance(fiber: Fiber): void,
29 - prepareToHydrateHostInstance(fiber: Fiber, rootContainerInstance: C): boolean,
29 + prepareToHydrateHostInstance(
30 + fiber: Fiber,
31 + rootContainerInstance: C,
32 + hostContext: CX,
33 + ): boolean,
34 prepareToHydrateHostTextInstance(fiber: Fiber): boolean,
35 popHydrationState(fiber: Fiber): boolean,
36 };
37
38 module.exports = function<T, P, I, TI, PI, C, CX, PL>(
39 config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
36 -): HydrationContext<C> {
40 +): HydrationContext<C, CX> {
41 const {
42 shouldSetTextContent,
43 canHydrateInstance,
@@ -218,6 +222,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
222 function prepareToHydrateHostInstance(
223 fiber: Fiber,
224 rootContainerInstance: C,
225 + hostContext: CX,
226 ): boolean {
227 const instance: I = fiber.stateNode;
228 const updatePayload = hydrateInstance(
@@ -225,6 +230,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
230 fiber.type,
231 fiber.memoizedProps,
232 rootContainerInstance,
233 + hostContext,
234 fiber,
235 );
236 // TODO: Type this specific to this type of component.
src/renderers/shared/fiber/ReactFiberReconciler.js
+1
@@ -133,6 +133,7 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
133 type: T,
134 props: P,
135 rootContainerInstance: C,
136 + hostContext: CX,
137 internalInstanceHandle: OpaqueHandle,
138 ) => null | PL,
139 hydrateTextInstance?: (
src/renderers/shared/fiber/ReactFiberScheduler.js
+1 -1
@@ -152,7 +152,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
152 config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
153 ) {
154 const hostContext = ReactFiberHostContext(config);
155 - const hydrationContext: HydrationContext<C> = ReactFiberHydrationContext(
155 + const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
156 config,
157 );
158 const {popHostContainer, popHostContext, resetHostContainer} = hostContext;