-
Notifications
You must be signed in to change notification settings - Fork 46.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Fiber] Nesting validation warnings #8586
Conversation
I don't think we should under estimate how dangerous the complexity of DEV only things can be if we're not careful. They can sometimes make it difficult to see how code could be simplified because they add a lot of noise. We should also be careful about exposing too much to host config. I only exposed the internal instance for the event system because we really needed it for event system compatibility but I think we should be very hesitant to expose more internals or even make any more use of the internal instance handle. That said we can probably find a simple way to implement it. This particular warning is also not really that relevant outside server rendering. |
IMO it's useful on client side (for example I often put a tags into a tags by mistake and this screws up the browser). But I don't think I fully understand why the logic for the message is so complex. I'll see if I can just use the element stack instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the owner stack we can probably just do the full parent stack (using the standard helper) – that includes host components right?
if (hostContext) { | ||
validateDOMNesting(type, null, internalInstanceHandle, hostContext.ancestorInfo,); | ||
} | ||
if (typeof props.children === 'string' || typeof props.children === 'number') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this just live in createTextInstance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No because this is the "single child" case in which we don't create an instance but use setTextContent
.
Yea, doing exactly that now, seems to work well (haven't pushed yet). |
@gaearon It only screws up the browser if you put invalid tags in |
I already figured and was very surprised. 😄 |
We could disable it in Stack too when I'm not sure we should because now it's harder to move to server rendering after the fact but I'm just saying it's plausible. |
Okay, pushed a new version. It doesn't require passing Fibers to renderer, and uses the element stack. Still not happy about DEV branches (and Flow hacks we'd have to add for them) but I can add some more production tests for this to make sure we don't regress. WDYT? |
(If this approach is cool I can also make Stack use element stack here too.) |
getChildHostContext(parentHostContext : string | null, type : string) { | ||
const parentNamespace = parentHostContext; | ||
return getChildNamespace(parentNamespace, type); | ||
getRootHostContext(rootContainerInstance) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One benefit of this is that we should be able to mount SVG content into an svg
root now.
Can add tests for this.
) : boolean { | ||
if (__DEV__) { | ||
if (oldProps.children !== newProps.children && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should probably check that we're moving to having a non-reified child here instead of always running the check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also ^^^ the day I started saying "reified". What have you done to me.
|
||
function flattenChildren(children) { | ||
var content = ''; | ||
|
||
// Flatten children and warn if they aren't strings or numbers; | ||
// invalid types are ignored. | ||
// We can silently skip them because invalid DOM nesting warning | ||
// catches these cases in Fiber. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a bit easier to keep nesting checks in ReactDOMFiber
than shoving them inside ReactDOMFiberComponent
. As a result I'm dealing with rawProps
rather than "host props" and validate them earlier than in Stack. I could have fixed this but it felt like the nesting check is already generic enough that we can just remove this special case. I could also remove it in Stack if needed.
const isMountingIntoDocument = rootContainerInstance.ownerDocument.documentElement === rootContainerInstance; | ||
const ancestorInfo = getChildAncestorInfo(null, isMountingIntoDocument ? '#document' : type); | ||
return {namespace, ancestorInfo}; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We don't really use else
with DEV
blocks because it didn't use to work in our transforms but I actually prefer that pattern because it enforces the notion that __DEV__
is only additional and doesn't change behavior. For this case I tend to prefer to use an early return instead. Although in this case I guess it severely changes the whole return type.
@@ -316,6 +318,49 @@ if (__DEV__) { | |||
return stack; | |||
}; | |||
|
|||
var getOwnerInfo = function(childInstance, childTag, ancestorInstance, ancestorTag, isParent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the Stack-specific part extracted from main function.
In Fiber instances are null and we use the current element stack instead.
@@ -485,6 +485,10 @@ const ARTRenderer = ReactFiberReconciler({ | |||
// Noop | |||
}, | |||
|
|||
getRootHostContext() { | |||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC you wanted something like this anyway.
@@ -731,4 +733,20 @@ var ReactDOMFiberComponent = { | |||
|
|||
}; | |||
|
|||
if (__DEV__) { | |||
ReactDOMFiberComponent.getChildAncestorInfo = function(parentAncestorInfo, type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like these forwarding things. We have so much of these in the event system that just adds bloat, add risk for cyclic dependencies and confusion.
Do you have a plan with these or can you just move this to ReactDOMFiber
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, the only reason for a separate ReactDOMFiberComponent file is to keep parity with Stack so that we can easier replicate changes. Should get rid of this file later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I thought I'd need them inside but then turned out I didn't. Will remove.
} = config; | ||
|
||
// Context stack is reused across the subtrees. | ||
// We use a null sentinel on the fiber stack to separate them. | ||
let contextFibers : Array<Fiber | null> | null = null; | ||
let contextValues : Array<CX | null> | null = null; | ||
let contextFibers : Array<Fiber | null> = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we now always use this for the root instance.
if (__DEV__) { | ||
const namespace = getChildNamespace(null, type); | ||
const isMountingIntoDocument = rootContainerInstance.ownerDocument.documentElement === rootContainerInstance; | ||
const ancestorInfo = getChildAncestorInfo(null, isMountingIntoDocument ? '#document' : type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the child nesting validation not case about the namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It doesn't (in Stack neither), and this is a bug. Because that should be valid: https://jsfiddle.net/q7e61jpd/.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, some nits but this strategy seems fine.
This makes it easier to avoid accidental nulls. I also added a test for the production case to avoid regressing because of __DEV__ branches.
The last few commits are ready for final review. |
I'll just get this in and address issues in a follow up (if any). |
const ancestorInfo = updatedAncestorInfo(null, isMountingIntoDocument ? '#document' : type, null); | ||
return {namespace, ancestorInfo}; | ||
} | ||
return getChildNamespace(null, type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gaearon This doesn't seem right. If I'm rendering SVG into an SVG tree, then the current namespace isn't HTML and the tag name isn't enough to determine it since I could be rendering into any SVG root. It seems to me that we should be reading the namespace of the root instead of the tagName.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dirty right now, will clean up tomorrow. But it almost works.
Not very happy with different host context types in DEV and PROD but I don't know how else to avoid extra allocations in PROD.
It is annoying to have to recalculate own context for non-reified text in host components so I probably need to pass own context to hosts. Also I might need to add something like
getRootHostContext
to avoid passing root instance to everygetChildContext
.