From 0c11baa6abea41132d0c894264783041ca852548 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Sat, 22 Oct 2022 15:19:42 -0700 Subject: [PATCH] add warnings for non-resources rendered outside body or head (#25532) Adds some clarifying warnings when you render a component that is almost a resource but isn't and the element was rendered outside the main document tree (outside of `` or `` --- .../src/client/ReactDOMFloatClient.js | 56 ++++++++- .../src/client/validateDOMNesting.js | 13 +- .../src/__tests__/ReactDOMFloat-test.js | 112 ++++++++++++++++++ .../src/ReactFiberHostContext.js | 16 ++- .../src/ReactFiberHostContext.new.js | 4 +- .../src/ReactFiberHostContext.old.js | 4 +- 6 files changed, 195 insertions(+), 10 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index c9681bb27c836..58a73189ec9c0 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -29,7 +29,11 @@ import { markNodeAsResource, } from './ReactDOMComponentTree'; import {HTML_NAMESPACE} from '../shared/DOMNamespaces'; -import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; +import { + getCurrentRootHostContainer, + getHostContext, +} from 'react-reconciler/src/ReactFiberHostContext'; +import {getResourceFormOnly} from './validateDOMNesting'; // The resource types we support. currently they match the form for the as argument. // In the future this may need to change, especially when modules / scripts are supported @@ -1331,6 +1335,11 @@ function insertResourceInstanceBefore( } export function isHostResourceType(type: string, props: Props): boolean { + let resourceFormOnly: boolean; + if (__DEV__) { + const hostContext = getHostContext(); + resourceFormOnly = getResourceFormOnly(hostContext); + } switch (type) { case 'meta': case 'title': { @@ -1339,14 +1348,29 @@ export function isHostResourceType(type: string, props: Props): boolean { case 'link': { const {onLoad, onError} = props; if (onLoad || onError) { + if (__DEV__) { + if (resourceFormOnly) { + console.error( + 'Cannot render a with onLoad or onError listeners outside the main document.' + + ' Try removing onLoad={...} and onError={...} or moving it into the root tag or' + + ' somewhere in the .', + ); + } + } return false; } switch (props.rel) { case 'stylesheet': { + const {href, precedence, disabled} = props; if (__DEV__) { validateLinkPropsForStyleResource(props); + if (typeof precedence !== 'string' && resourceFormOnly) { + console.error( + 'Cannot render a outside the main document without knowing its precedence.' + + ' Consider adding precedence="default" or moving it into the root tag.', + ); + } } - const {href, precedence, disabled} = props; return ( typeof href === 'string' && typeof precedence === 'string' && @@ -1363,8 +1387,36 @@ export function isHostResourceType(type: string, props: Props): boolean { // We don't validate because it is valid to use async with onLoad/onError unlike combining // precedence with these for style resources const {src, async, onLoad, onError} = props; + if (__DEV__) { + if (async !== true && resourceFormOnly) { + console.error( + 'Cannot render a sync or defer