Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/@react-aria/collections/src/Hidden.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ import React, {Context, createContext, forwardRef, JSX, ReactElement, ReactNode,
// It will throw an error during hydration when it expects the firstChild to contain content rendered
// on the server, when in reality, the browser will have placed this inside the `content` document fragment.
// This monkey patches the firstChild property for our special hidden template elements to work around this error.
// does the same for appendChild/removeChild/insertBefore as per the issue below
// See https://github.com/facebook/react/issues/19932
if (typeof HTMLTemplateElement !== 'undefined') {
const getFirstChild = Object.getOwnPropertyDescriptor(Node.prototype, 'firstChild')!.get!;
const originalAppendChild = Object.getOwnPropertyDescriptor(Node.prototype, 'appendChild')!.value!;
const originalRemoveChild = Object.getOwnPropertyDescriptor(Node.prototype, 'removeChild')!.value!;
const originalInsertBefore = Object.getOwnPropertyDescriptor(Node.prototype, 'insertBefore')!.value!;

Object.defineProperty(HTMLTemplateElement.prototype, 'firstChild', {
configurable: true,
enumerable: true,
Expand All @@ -31,6 +36,46 @@ if (typeof HTMLTemplateElement !== 'undefined') {
}
}
});

Object.defineProperty(HTMLTemplateElement.prototype, 'appendChild', {
configurable: true,
enumerable: true,
value: function (node) {
if (this.dataset.reactAriaHidden) {
return this.content.appendChild(node);
} else {
return originalAppendChild.call(this, node);
}
}
});

Object.defineProperty(HTMLTemplateElement.prototype, 'removeChild', {
configurable: true,
enumerable: true,
value: function (node) {
if (this.dataset.reactAriaHidden) {
return this.content.removeChild(node);
} else {
return originalRemoveChild.call(this, node);
}
}
});

Object.defineProperty(HTMLTemplateElement.prototype, 'insertBefore', {
configurable: true,
enumerable: true,
value: function (node, child) {
if (this.dataset.reactAriaHidden) {
// child might not exist in this.content for some reason (stale?), add to end instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

In this case, what is the child and it's parent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

the child is the bottom most div, at the time of the error this.content is a document fragment with no children hence why it breaks trying to call insertBefore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, this seems wrong. <template> doesn't support direct children, they should be within the document fragment. That's what these overridden methods should be doing (since react doesn't handle that). So maybe we're missing another one and that's how those dives are getting there?

if (child && !this.content.contains(child)) {
return this.content.appendChild(node);
}
return this.content.insertBefore(node, child);
} else {
return originalInsertBefore.call(this, node, child);
}
}
});
}

export const HiddenContext: Context<boolean> = createContext<boolean>(false);
Expand Down