@samitouri / QOS-React / commits / 0032b2a3ee

[Flight] Log error if prod elements are rendered (#34189)

Sebastian "Sebbie" Silbermann committed Aug 13, 2025 at 08:47 UTC 0032b2a3eec4723c433c16a738cf17e33ad742c3
3 files changed +46 -1
packages/internal-test-utils/consoleMock.js
+1 -1
@@ -355,7 +355,7 @@ export function createLogAssertion(
355 let argIndex = 0;
356 // console.* could have been called with a non-string e.g. `console.error(new Error())`
357 // eslint-disable-next-line react-internal/safe-string-coercion
358 - String(format).replace(/%s|%c/g, () => argIndex++);
358 + String(format).replace(/%s|%c|%o/g, () => argIndex++);
359 if (argIndex !== args.length) {
360 if (format.includes('%c%s')) {
361 // We intentionally use mismatching formatting when printing badging because we don't know
packages/react-server/src/ReactFlightServer.js
+21
@@ -3354,6 +3354,27 @@ function renderModelDestructive(
3354 task.debugOwner = element._owner;
3355 task.debugStack = element._debugStack;
3356 task.debugTask = element._debugTask;
3357 + if (
3358 + element._owner === undefined ||
3359 + element._debugStack === undefined ||
3360 + element._debugTask === undefined
3361 + ) {
3362 + let key = '';
3363 + if (element.key !== null) {
3364 + key = ' key="' + element.key + '"';
3365 + }
3366 +
3367 + console.error(
3368 + 'Attempted to render <%s%s> without development properties. ' +
3369 + 'This is not supported. It can happen if:' +
3370 + '\n- The element is created with a production version of React but rendered in development.' +
3371 + '\n- The element was cloned with a custom function instead of `React.cloneElement`.\n' +
3372 + 'The props of this element may help locate this element: %o',
3373 + element.type,
3374 + key,
3375 + element.props,
3376 + );
3377 + }
3378 // TODO: Pop this. Since we currently don't have a point where we can pop the stack
3379 // this debug information will be used for errors inside sibling properties that
3380 // are not elements. Leading to the wrong attribution on the server. We could fix
packages/react-server/src/__tests__/ReactFlightServer-test.js
+24
@@ -36,6 +36,7 @@ let ReactNoopFlightServer;
36 let Scheduler;
37 let advanceTimersByTime;
38 let assertLog;
39 +let assertConsoleErrorDev;
40
41 describe('ReactFlight', () => {
42 beforeEach(() => {
@@ -64,6 +65,7 @@ describe('ReactFlight', () => {
65 Scheduler = require('scheduler');
66 const InternalTestUtils = require('internal-test-utils');
67 assertLog = InternalTestUtils.assertLog;
68 + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
69 });
70
71 afterEach(() => {
@@ -175,4 +177,26 @@ describe('ReactFlight', () => {
177 stackTwo: '\n in OwnerStackDelayed (at **)' + '\n in App (at **)',
178 });
179 });
180 +
181 + it('logs an error when prod elements are rendered', async () => {
182 + const element = ReactServer.createElement('span', {
183 + key: 'one',
184 + children: 'Free!',
185 + });
186 + ReactNoopFlightServer.render(
187 + // bad clone
188 + {...element},
189 + );
190 +
191 + assertConsoleErrorDev([
192 + [
193 + 'Attempted to render <span key="one"> without development properties. This is not supported. It can happen if:' +
194 + '\n- The element is created with a production version of React but rendered in development.' +
195 + '\n- The element was cloned with a custom function instead of `React.cloneElement`.\n' +
196 + "The props of this element may help locate this element: { children: 'Free!', [key]: [Getter] }",
197 + {withoutStack: true},
198 + ],
199 + "TypeError: Cannot read properties of undefined (reading 'stack')",
200 + ]);
201 + });
202 });