Repository: facebook/react
Pull request: update error message to include useLayoutEffect or useEffect on bad e…

## Summary

See conversation here: https://github.com/reactwg/react-18/discussions/95#discussioncomment-1293608

In react, when an effect returns something unexpected like a promise we show an error like:

>  An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately

The problem is that this error message says "useEffect" even when using "useLayoutEffect". To fix, we can check the effect tag to see if it's a layout or passive effect.

See: https://codesandbox.io/s/effect-destroy-function-type-forked-dwpjm?file=/src/App.js

## How did you test this change?

Updating existing test cases to expect the updated error message

Changed files:
filename: packages/react-reconciler/src/ReactFiberCommitWork.new.js
patch: @@ -507,7 +507,7 @@ function commitHookEffectListUnmount(
   }
 }
 
-function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
+function commitHookEffectListMount(tag: HookFlags, finishedWork: Fiber) {
   const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
   const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
   if (lastEffect !== null) {
@@ -522,17 +522,26 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
         if (__DEV__) {
           const destroy = effect.destroy;
           if (destroy !== undefined && typeof destroy !== 'function') {
+            let hookName;
+            if ((effect.tag & HookLayout) !== NoFlags) {
+              hookName = 'useLayoutEffect';
+            } else {
+              hookName = 'useEffect';
+            }
             let addendum;
             if (destroy === null) {
               addendum =
                 ' You returned null. If your effect does not require clean ' +
                 'up, return undefined (or nothing).';
             } else if (typeof destroy.then === 'function') {
               addendum =
-                '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
+                '\n\nIt looks like you wrote ' +
+                hookName +
+                '(async () => ...) or returned a Promise. ' +
                 'Instead, write the async function inside your effect ' +
                 'and call it immediately:\n\n' +
-                'useEffect(() => {\n' +
+                hookName +
+                '(() => {\n' +
                 '  async function fetchData() {\n' +
                 '    // You can await here\n' +
                 '    const response = await MyAPI.getData(someId);\n' +
@@ -545,8 +554,9 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
               addendum = ' You returned: ' + destroy;
             }
             console.error(
-              'An effect function must not return anything besides a function, ' +
+              '%s must not return anything besides a function, ' +
                 'which is used for clean-up.%s',
+              hookName,
               addendum,
             );
           }

filename: packages/react-reconciler/src/ReactFiberCommitWork.old.js
patch: @@ -507,7 +507,7 @@ function commitHookEffectListUnmount(
   }
 }
 
-function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
+function commitHookEffectListMount(tag: HookFlags, finishedWork: Fiber) {
   const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
   const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
   if (lastEffect !== null) {
@@ -522,17 +522,26 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
         if (__DEV__) {
           const destroy = effect.destroy;
           if (destroy !== undefined && typeof destroy !== 'function') {
+            let hookName;
+            if ((effect.tag & HookLayout) !== NoFlags) {
+              hookName = 'useLayoutEffect';
+            } else {
+              hookName = 'useEffect';
+            }
             let addendum;
             if (destroy === null) {
               addendum =
                 ' You returned null. If your effect does not require clean ' +
                 'up, return undefined (or nothing).';
             } else if (typeof destroy.then === 'function') {
               addendum =
-                '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
+                '\n\nIt looks like you wrote ' +
+                hookName +
+                '(async () => ...) or returned a Promise. ' +
                 'Instead, write the async function inside your effect ' +
                 'and call it immediately:\n\n' +
-                'useEffect(() => {\n' +
+                hookName +
+                '(() => {\n' +
                 '  async function fetchData() {\n' +
                 '    // You can await here\n' +
                 '    const response = await MyAPI.getData(someId);\n' +
@@ -545,8 +554,9 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
               addendum = ' You returned: ' + destroy;
             }
             console.error(
-              'An effect function must not return anything besides a function, ' +
+              '%s must not return anything besides a function, ' +
                 'which is used for clean-up.%s',
+              hookName,
               addendum,
             );
           }

filename: packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
patch: @@ -2647,7 +2647,7 @@ describe('ReactHooksWithNoopRenderer', () => {
           root1.render(<App return={17} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useEffect must not return anything besides a ' +
           'function, which is used for clean-up. You returned: 17',
       ]);
 
@@ -2657,7 +2657,7 @@ describe('ReactHooksWithNoopRenderer', () => {
           root2.render(<App return={null} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useEffect must not return anything besides a ' +
           'function, which is used for clean-up. You returned null. If your ' +
           'effect does not require clean up, return undefined (or nothing).',
       ]);
@@ -2668,7 +2668,7 @@ describe('ReactHooksWithNoopRenderer', () => {
           root3.render(<App return={Promise.resolve()} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useEffect must not return anything besides a ' +
           'function, which is used for clean-up.\n\n' +
           'It looks like you wrote useEffect(async () => ...) or returned a Promise.',
       ]);
@@ -2873,7 +2873,7 @@ describe('ReactHooksWithNoopRenderer', () => {
           root1.render(<App return={17} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useLayoutEffect must not return anything besides a ' +
           'function, which is used for clean-up. You returned: 17',
       ]);
 
@@ -2883,7 +2883,7 @@ describe('ReactHooksWithNoopRenderer', () => {
           root2.render(<App return={null} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useLayoutEffect must not return anything besides a ' +
           'function, which is used for clean-up. You returned null. If your ' +
           'effect does not require clean up, return undefined (or nothing).',
       ]);
@@ -2894,9 +2894,9 @@ describe('ReactHooksWithNoopRenderer', () => {
           root3.render(<App return={Promise.resolve()} />);
         }),
       ).toErrorDev([
-        'Warning: An effect function must not return anything besides a ' +
+        'Warning: useLayoutEffect must not return anything besides a ' +
           'function, which is used for clean-up.\n\n' +
-          'It looks like you wrote useEffect(async () => ...) or returned a Promise.',
+          'It looks like you wrote useLayoutEffect(async () => ...) or returned a Promise.',
       ]);
 
       // Error on unmount because React assumes the value is a function