Skip to content

Commit

Permalink
Move createElement/JSX Warnings into the Renderer (#29088)
Browse files Browse the repository at this point in the history
This is necessary to simplify the component stack handling to make way
for owner stacks. It also solves some hacks that we used to have but
don't quite make sense. It also solves the problem where things like key
warnings get silenced in RSC because they get deduped. It also surfaces
areas where we were missing key warnings to begin with.

Almost every type of warning is issued from the renderer. React Elements
are really not anything special themselves. They're just lazily invoked
functions and its really the renderer that determines there semantics.

We have three types of warnings that previously fired in
JSX/createElement:

- Fragment props validation.
- Type validation.
- Key warning.

It's nice to be able to do some validation in the JSX/createElement
because it has a more specific stack frame at the callsite. However,
that's the case for every type of component and validation. That's the
whole point of enableOwnerStacks. It's also not sufficient to do it in
JSX/createElement so we also have validation in the renderers too. So
this validation is really just an eager validation but also happens
again later.

The problem with these is that we don't really know what types are valid
until we get to the renderer. Additionally, by placing it in the
isomorphic code it becomes harder to do deduping of warnings in a way
that makes sense for that renderer. It also means we can't reuse logic
for managing stacks etc.

Fragment props validation really should just be part of the renderer
like any other component type. This also matters once we add Fragment
refs and other fragment features. So I moved this into Fiber. However,
since some Fragments don't have Fibers, I do the validation in
ChildFiber instead of beginWork where it would normally happen.

For `type` validation we already do validation when rendering. By
leaving it to the renderer we don't have to hard code an extra list.
This list also varies by context. E.g. class components aren't allowed
in RSC but client references are but we don't have an isomorphic way to
identify client references because they're defined by the host config so
the current logic is flawed anyway. I kept the early validation for now
without the `enableOwnerStacks` since it does provide a nicer stack
frame but with that flag on it'll be handled with nice stacks anyway. I
normalized some of the errors to ensure tests pass.

For `key` validation it's the same principle. The mechanism for the
heuristic is still the same - if it passes statically through a parent
JSX/createElement call then it's considered validated. We already did
print the error later from the renderer so this also disables the early
log in the `enableOwnerStacks` flag.

I also added logging to Fizz so that key warnings can print in SSR logs.

Flight is a bit more complex. For elements that end up on the client we
just pass the `validated` flag along to the client and let the client
renderer print the error once rendered. For server components we log the
error from Flight with the server component as the owner on the stack
which will allow us to print the right stack for context. The factoring
of this is a little tricky because we only want to warn if it's in an
array parent but we want to log the error later to get the right debug
info.

Fiber/Fizz has a similar factoring problem that causes us to create a
fake Fiber for the owner which means the logs won't be associated with
the right place in DevTools.

DiffTrain build for [84239da](84239da)
  • Loading branch information
sebmarkbage committed May 23, 2024
1 parent 2de2c20 commit e574453
Show file tree
Hide file tree
Showing 39 changed files with 1,710 additions and 569 deletions.
113 changes: 35 additions & 78 deletions compiled/facebook-www/JSXDEVRuntime-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ function printWarning(level, format, args) {

var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; // Defensive in case this is fired before React is initialized.

if (ReactSharedInternals != null) {
var stack = ReactSharedInternals.getStackAddendum();
if (ReactSharedInternals != null && ReactSharedInternals.getCurrentStack) {
var stack = ReactSharedInternals.getCurrentStack();

if (stack !== '') {
format += '%s';
Expand Down Expand Up @@ -318,7 +318,9 @@ function checkPropStringCoercion(value, propName) {
}
}

var REACT_CLIENT_REFERENCE$1 = Symbol.for('react.client.reference');
var REACT_CLIENT_REFERENCE$1 = Symbol.for('react.client.reference'); // This function is deprecated. Don't use. Only the renderer knows what a valid type is.
// TODO: Delete this when enableOwnerStacks ships.

function isValidElementType(type) {
if (typeof type === 'string' || typeof type === 'function') {
return true;
Expand Down Expand Up @@ -716,7 +718,8 @@ function describeFunctionComponentFrame(fn) {
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
} // TODO: Delete this once the key warning no longer uses it. I.e. when enableOwnerStacks ship.


function describeUnknownElementTypeFrameInDEV(type) {

Expand Down Expand Up @@ -1154,7 +1157,7 @@ function ReactElement(type, key, _ref, self, source, owner, props, debugStack, d
configurable: false,
enumerable: false,
writable: true,
value: false
value: 0
}); // debugInfo contains Server Component debug information.

Object.defineProperty(element, '_debugInfo', {
Expand Down Expand Up @@ -1346,29 +1349,7 @@ function jsxDEV$1(type, config, maybeKey, isStaticChildren, source, self) {
}
}

var element = ReactElement(type, key, ref, self, source, getOwner(), props);

if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
}

return element;
}
}

function getDeclarationErrorAddendum() {
{
var owner = getOwner();

if (owner) {
var name = getComponentNameFromType(owner.type);

if (name) {
return '\n\nCheck the render method of `' + name + '`.';
}
}

return '';
return ReactElement(type, key, ref, self, source, getOwner(), props);
}
}
/**
Expand All @@ -1381,7 +1362,6 @@ function getDeclarationErrorAddendum() {
* @param {*} parentType node's parent's type.
*/


function validateChildKeys(node, parentType) {
{
if (typeof node !== 'object' || !node) {
Expand All @@ -1399,7 +1379,7 @@ function validateChildKeys(node, parentType) {
} else if (isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
node._store.validated = 1;
}
} else {
var iteratorFn = getIteratorFn(node);
Expand Down Expand Up @@ -1450,12 +1430,13 @@ var ownerHasKeyUseWarning = {};
*/

function validateExplicitKey(element, parentType) {

{
if (!element._store || element._store.validated || element.key != null) {
return;
}

element._store.validated = true;
element._store.validated = 1;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);

if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
Expand All @@ -1481,28 +1462,37 @@ function validateExplicitKey(element, parentType) {
childOwner = " It was passed a child from " + ownerName + ".";
}

setCurrentlyValidatingElement(element);
var prevGetCurrentStack = ReactSharedInternals.getCurrentStack;

error('Each child in a list should have a unique "key" prop.' + '%s%s See https://react.dev/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
ReactSharedInternals.getCurrentStack = function () {

setCurrentlyValidatingElement(null);
}
}
var stack = describeUnknownElementTypeFrameInDEV(element.type); // Delegate to the injected renderer-specific implementation

function setCurrentlyValidatingElement(element) {
{
if (element) {
var stack = describeUnknownElementTypeFrameInDEV(element.type);
ReactSharedInternals.setExtraStackFrame(stack);
} else {
ReactSharedInternals.setExtraStackFrame(null);
}
if (prevGetCurrentStack) {
stack += prevGetCurrentStack() || '';
}

return stack;
};

error('Each child in a list should have a unique "key" prop.' + '%s%s See https://react.dev/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);

ReactSharedInternals.getCurrentStack = prevGetCurrentStack;
}
}

function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
var info = '';
var owner = getOwner();

if (owner) {
var name = getComponentNameFromType(owner.type);

if (name) {
info = '\n\nCheck the render method of `' + name + '`.';
}
}

if (!info) {
var parentName = getComponentNameFromType(parentType);
Expand All @@ -1515,39 +1505,6 @@ function getCurrentComponentErrorInfo(parentType) {
return info;
}
}
/**
* Given a fragment, validate that it can only be provided with fragment props
* @param {ReactElement} fragment
*/


function validateFragmentProps(fragment) {
// TODO: Move this to render phase instead of at element creation.
{
var keys = Object.keys(fragment.props);

for (var i = 0; i < keys.length; i++) {
var key = keys[i];

if (key !== 'children' && key !== 'key') {
setCurrentlyValidatingElement(fragment);

error('Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key);

setCurrentlyValidatingElement(null);
break;
}
}

if (!enableRefAsProp && fragment.ref !== null) {
setCurrentlyValidatingElement(fragment);

error('Invalid attribute `ref` supplied to `React.Fragment`.');

setCurrentlyValidatingElement(null);
}
}
}

function coerceStringRef(mixedRef, owner, type) {

Expand Down
113 changes: 35 additions & 78 deletions compiled/facebook-www/JSXDEVRuntime-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ function printWarning(level, format, args) {

var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; // Defensive in case this is fired before React is initialized.

if (ReactSharedInternals != null) {
var stack = ReactSharedInternals.getStackAddendum();
if (ReactSharedInternals != null && ReactSharedInternals.getCurrentStack) {
var stack = ReactSharedInternals.getCurrentStack();

if (stack !== '') {
format += '%s';
Expand Down Expand Up @@ -318,7 +318,9 @@ function checkPropStringCoercion(value, propName) {
}
}

var REACT_CLIENT_REFERENCE$1 = Symbol.for('react.client.reference');
var REACT_CLIENT_REFERENCE$1 = Symbol.for('react.client.reference'); // This function is deprecated. Don't use. Only the renderer knows what a valid type is.
// TODO: Delete this when enableOwnerStacks ships.

function isValidElementType(type) {
if (typeof type === 'string' || typeof type === 'function') {
return true;
Expand Down Expand Up @@ -716,7 +718,8 @@ function describeFunctionComponentFrame(fn) {
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
} // TODO: Delete this once the key warning no longer uses it. I.e. when enableOwnerStacks ship.


function describeUnknownElementTypeFrameInDEV(type) {

Expand Down Expand Up @@ -1157,7 +1160,7 @@ function ReactElement(type, key, _ref, self, source, owner, props, debugStack, d
configurable: false,
enumerable: false,
writable: true,
value: false
value: 0
}); // debugInfo contains Server Component debug information.

Object.defineProperty(element, '_debugInfo', {
Expand Down Expand Up @@ -1349,29 +1352,7 @@ function jsxDEV$1(type, config, maybeKey, isStaticChildren, source, self) {
}
}

var element = ReactElement(type, key, ref, self, source, getOwner(), props);

if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
}

return element;
}
}

function getDeclarationErrorAddendum() {
{
var owner = getOwner();

if (owner) {
var name = getComponentNameFromType(owner.type);

if (name) {
return '\n\nCheck the render method of `' + name + '`.';
}
}

return '';
return ReactElement(type, key, ref, self, source, getOwner(), props);
}
}
/**
Expand All @@ -1384,7 +1365,6 @@ function getDeclarationErrorAddendum() {
* @param {*} parentType node's parent's type.
*/


function validateChildKeys(node, parentType) {
{
if (typeof node !== 'object' || !node) {
Expand All @@ -1402,7 +1382,7 @@ function validateChildKeys(node, parentType) {
} else if (isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
node._store.validated = 1;
}
} else {
var iteratorFn = getIteratorFn(node);
Expand Down Expand Up @@ -1453,12 +1433,13 @@ var ownerHasKeyUseWarning = {};
*/

function validateExplicitKey(element, parentType) {

{
if (!element._store || element._store.validated || element.key != null) {
return;
}

element._store.validated = true;
element._store.validated = 1;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);

if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
Expand All @@ -1484,28 +1465,37 @@ function validateExplicitKey(element, parentType) {
childOwner = " It was passed a child from " + ownerName + ".";
}

setCurrentlyValidatingElement(element);
var prevGetCurrentStack = ReactSharedInternals.getCurrentStack;

error('Each child in a list should have a unique "key" prop.' + '%s%s See https://react.dev/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
ReactSharedInternals.getCurrentStack = function () {

setCurrentlyValidatingElement(null);
}
}
var stack = describeUnknownElementTypeFrameInDEV(element.type); // Delegate to the injected renderer-specific implementation

function setCurrentlyValidatingElement(element) {
{
if (element) {
var stack = describeUnknownElementTypeFrameInDEV(element.type);
ReactSharedInternals.setExtraStackFrame(stack);
} else {
ReactSharedInternals.setExtraStackFrame(null);
}
if (prevGetCurrentStack) {
stack += prevGetCurrentStack() || '';
}

return stack;
};

error('Each child in a list should have a unique "key" prop.' + '%s%s See https://react.dev/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);

ReactSharedInternals.getCurrentStack = prevGetCurrentStack;
}
}

function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
var info = '';
var owner = getOwner();

if (owner) {
var name = getComponentNameFromType(owner.type);

if (name) {
info = '\n\nCheck the render method of `' + name + '`.';
}
}

if (!info) {
var parentName = getComponentNameFromType(parentType);
Expand All @@ -1518,39 +1508,6 @@ function getCurrentComponentErrorInfo(parentType) {
return info;
}
}
/**
* Given a fragment, validate that it can only be provided with fragment props
* @param {ReactElement} fragment
*/


function validateFragmentProps(fragment) {
// TODO: Move this to render phase instead of at element creation.
{
var keys = Object.keys(fragment.props);

for (var i = 0; i < keys.length; i++) {
var key = keys[i];

if (key !== 'children' && key !== 'key') {
setCurrentlyValidatingElement(fragment);

error('Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key);

setCurrentlyValidatingElement(null);
break;
}
}

if (!enableRefAsProp && fragment.ref !== null) {
setCurrentlyValidatingElement(fragment);

error('Invalid attribute `ref` supplied to `React.Fragment`.');

setCurrentlyValidatingElement(null);
}
}
}

function coerceStringRef(mixedRef, owner, type) {

Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2e540e22b2b4038a278b2875306976b016fb31a9
84239da896fd7395a667ab1e7ef1ef338a32de8f
Loading

0 comments on commit e574453

Please sign in to comment.