Repository: vercel/next.js
Pull request: [test] Add failing test for `useActionState` with `'use cache'`

Adds a test case demonstrating that `useActionState` doesn't work correctly when the `'use cache'` function is exported separately, due to wrong function arity information in the server reference information byte. This is currently a limitation of the Next.js compiler, which will be addressed in a future PR.

Also cleans up `console.log` from the existing `useActionState` test.

Changed files:
filename: test/e2e/app-dir/use-cache/app/(partially-static)/use-action-state-separate-export/cached.ts
patch: @@ -0,0 +1,7 @@
+'use cache'
+
+const getRandomValue = async () => {
+  return Math.random()
+}
+
+export { getRandomValue }

filename: test/e2e/app-dir/use-cache/app/(partially-static)/use-action-state-separate-export/form.tsx
patch: @@ -0,0 +1,15 @@
+'use client'
+
+import { useActionState } from 'react'
+import { getRandomValue } from './cached'
+
+export function Form() {
+  const [result, formAction, isPending] = useActionState(getRandomValue, -1)
+
+  return (
+    <form action={formAction}>
+      <button id="submit-button">Submit</button>
+      <p>{isPending ? 'loading...' : result}</p>
+    </form>
+  )
+}

filename: test/e2e/app-dir/use-cache/app/(partially-static)/use-action-state-separate-export/page.tsx
patch: @@ -0,0 +1,5 @@
+import { Form } from './form'
+
+export default function Page() {
+  return <Form />
+}

filename: test/e2e/app-dir/use-cache/app/(partially-static)/use-action-state/cached.ts
patch: @@ -1,7 +1,5 @@
 'use cache'
 
 export async function getRandomValue() {
-  const v = Math.random()
-  console.log(v)
-  return v
+  return Math.random()
 }

filename: test/e2e/app-dir/use-cache/use-cache.test.ts
patch: @@ -499,6 +499,7 @@ describe('use-cache', () => {
           '/static-class-method',
           withCacheComponents && '/unhandled-promise-regression',
           '/use-action-state',
+          '/use-action-state-separate-export',
           '/with-server-action',
         ].filter(Boolean)
       )
@@ -709,6 +710,34 @@ describe('use-cache', () => {
     })
   })
 
+  // TODO: This test doesn't work currently because the compiler doesn't
+  // properly compute the server reference information byte that includes the
+  // function arity. Without this information, the client can't optimize the
+  // arguments it sends to the server, so the (unused) previous state is also
+  // sent as an argument, leading to cache misses.
+  it.failing(
+    'works with useActionState if previousState parameter is not used in "use cache" function (separate export)',
+    async () => {
+      const browser = await next.browser('/use-action-state-separate-export')
+
+      let value = await browser.elementByCss('p').text()
+      expect(value).toBe('-1')
+
+      await browser.elementByCss('button').click()
+
+      await retry(async () => {
+        value = await browser.elementByCss('p').text()
+        expect(value).toMatch(/\d\.\d+/)
+      })
+
+      await browser.elementByCss('button').click()
+
+      await retry(async () => {
+        expect(await browser.elementByCss('p').text()).toBe(value)
+      })
+    }
+  )
+
   it('works with "use cache" in method props', async () => {
     const browser = await next.browser('/method-props')