Repository: facebook/react
Pull request: Warn if outdated JSX transform is detected

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.

Changed files:
filename: packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
patch: @@ -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 {
@@ -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;
@@ -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;

filename: packages/react/src/__tests__/ReactCreateElement-test.js
patch: @@ -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});
+  });
 });

filename: packages/react/src/jsx/ReactJSXElement.js
patch: @@ -33,6 +33,7 @@ let specialPropKeyWarningShown;
 let specialPropRefWarningShown;
 let didWarnAboutStringRefs;
 let didWarnAboutElementRef;
+let didWarnAboutOldJSXRuntime;
 
 if (__DEV__) {
   didWarnAboutStringRefs = {};
@@ -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;