[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
});