Skip to content

Commit

Permalink
Warn if outdated JSX transform is detected (#28781)
Browse files Browse the repository at this point in the history
We want to warn if we detect that an app is using an outdated JSX
transform. We can't just warn if `createElement` is called because we
still support `createElement` when it's called manually. We only want to
warn if `createElement` is output by the compiler.

The heuristic is to check for a `__self` prop, which is an optional,
internal prop that older transforms used to pass to `createElement` for
better debugging in development mode.

If `__self` is present, we `console.warn` once with advice to upgrade to
the modern JSX transform. Subsequent elements will not warn.

There's a special case we have to account for: when a static "key" prop
is defined _after_ a spread, the modern JSX transform outputs
`createElement` instead of `jsx`. (This is because with `jsx`, a spread
key always takes precedence over a static key, regardless of the order,
whereas `createElement` respects the order.) To avoid a false positive
warning, we skip the warning whenever a `key` prop is present.
  • Loading branch information
acdlite authored and rickhanlonii committed Apr 11, 2024
1 parent 436f912 commit e481318
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ describe('ReactDeprecationWarnings', () => {
);
});

// Disabling this until #28732 lands so we can assert on the warning message.
// (It's already disabled in all but the Meta builds, anyway. Nbd.)
// @gate TODO || !__DEV__
// @gate !disableStringRefs
it('should warn when owner and self are the same for string refs', async () => {
class RefComponent extends React.Component {
Expand All @@ -114,8 +117,11 @@ describe('ReactDeprecationWarnings', () => {
await waitForAll([]);
});

// Disabling this until #28732 lands so we can assert on the warning message.
// (It's already disabled in all but the Meta builds, anyway. Nbd.)
// @gate TODO || !__DEV__
// @gate !disableStringRefs
it('should warn when owner and self are different for string refs', async () => {
it('should warn when owner and self are different for string refs (createElement)', async () => {
class RefComponent extends React.Component {
render() {
return null;
Expand Down Expand Up @@ -143,7 +149,7 @@ describe('ReactDeprecationWarnings', () => {

// @gate __DEV__
// @gate !disableStringRefs
it('should warn when owner and self are different for string refs', async () => {
it('should warn when owner and self are different for string refs (jsx)', async () => {
class RefComponent extends React.Component {
render() {
return null;
Expand Down
27 changes: 27 additions & 0 deletions packages/react/src/__tests__/ReactCreateElement-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,31 @@ describe('ReactCreateElement', () => {
});
expect(test.props.value).toBeNaN();
});

it('warns if outdated JSX transform is detected', async () => {
// Warns if __self is detected, because that's only passed by a compiler
expect(() => {
React.createElement('div', {className: 'foo', __self: this});
}).toWarnDev(
'Your app (or one of its dependencies) is using an outdated ' +
'JSX transform.',
{
withoutStack: true,
},
);

// Only warns the first time. Subsequent elements don't warn.
React.createElement('div', {className: 'foo', __self: this});
});

it('do not warn about outdated JSX transform if `key` is present', () => {
// When a static "key" prop is defined _after_ a spread, the modern JSX
// transform outputs `createElement` instead of `jsx`. (This is because with
// `jsx`, a spread key always takes precedence over a static key, regardless
// of the order, whereas `createElement` respects the order.)
//
// To avoid a false positive warning, we skip the warning whenever a `key`
// prop is present.
React.createElement('div', {key: 'foo', __self: this});
});
});
23 changes: 23 additions & 0 deletions packages/react/src/jsx/ReactJSXElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ let specialPropKeyWarningShown;
let specialPropRefWarningShown;
let didWarnAboutStringRefs;
let didWarnAboutElementRef;
let didWarnAboutOldJSXRuntime;

if (__DEV__) {
didWarnAboutStringRefs = {};
Expand Down Expand Up @@ -722,6 +723,28 @@ export function createElement(type, config, children) {
let ref = null;

if (config != null) {
if (__DEV__) {
if (
!didWarnAboutOldJSXRuntime &&
'__self' in config &&
// Do not assume this is the result of an oudated JSX transform if key
// is present, because the modern JSX transform sometimes outputs
// createElement to preserve precedence between a static key and a
// spread key. To avoid false positive warnings, we never warn if
// there's a key.
!('key' in config)
) {
didWarnAboutOldJSXRuntime = true;
console.warn(
'Your app (or one of its dependencies) is using an outdated JSX ' +
'transform. Update to the modern JSX transform for ' +
'faster performance: ' +
// TODO: Create a short link for this
'https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html',
);
}
}

if (hasValidRef(config)) {
if (!enableRefAsProp) {
ref = config.ref;
Expand Down

0 comments on commit e481318

Please sign in to comment.