Skip to content

feat(next): let server functions plug into useActionState - #2302

Merged
dinwwwh merged 3 commits into
mainfrom
claude/server-functions-useactionstate-6e8300
Oct 23, 2026
Merged

dinwwwh merged 3 commits into
mainfrom
claude/server-functions-useactionstate-6e8300

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Server functions can now be passed straight to React's useActionState. A two-argument call is treated as (prevState, input | FormData): the previous state is ignored and FormData is parsed with bracket notation. The state is the usual [error, data] tuple, so forms work, and show validation errors, before JavaScript loads.

const [state, submit, isPending] = useActionState(serverFunction, null)
const [error, data] = state ?? [null, undefined]
return <form action={submit}>...</form>

Behavior changes

  • One-argument calls are unchanged, including a single raw FormData.
  • A form action built with fn.bind(null, input) is now called as (input, formData), so the bound input is treated as the previous state and the form data is validated instead. The docs recommend hidden inputs.
  • Because ServerFunction now has two call signatures, a hand-written async function with a single parameter no longer satisfies the ServerFunction type (for example, when passed to useServerFunction). Nothing in the repo does this.
  • createServerFormFunction now parses with parseFormData, so all three FormData entry points share one parser. Its output is unchanged.

Docs

  • A short useActionState section in the Next.js integration page, with a warning about .bind.

Testing

  • A jsdom test drives the real React useActionState with FormData and plain input, and fails without this change. A unit test checks that a single FormData argument is still passed through unchanged.
  • Type tests for useActionState inference: null and tuple initial states, a mismatched initial state rejected, a procedure with no input. The existing hook type tests still pass.
  • Checked manually in the Next.js playground with a temporary page:
    • Plain form posts (no JavaScript) rendered both the success result, with tags[] parsed, and the Zod issue message.
    • With JavaScript, the submit went through a server-action fetch.

Server functions now accept a (prevState, input | FormData) call shape, so
they can be passed straight to React's useActionState and keep working
before JavaScript loads. Two-argument calls ignore the previous state and
deserialize FormData with bracket notation; one-argument calls are
unchanged. createServerFunction and createServerFormFunction now share
parseFormData for FormData parsing.

Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
orpc bad09e3 Commit Preview URL

Branch Preview URL
Oct 23 2026, 06:19 AM

@macroscopeapp

macroscopeapp Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This adds direct useActionState support and a new two-argument server-function contract, including FormData parsing and no-JavaScript form handling. The implementation is localized and tested, but the public type change may affect existing hand-written server-function implementations and merits compatibility review.

You can add or adjust custom eligibility rules. Learn more.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 22, 2026
@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new

pkg-pr-new Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2302

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2302

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2302

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2302

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2302

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2302

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2302

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2302

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2302

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2302

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2302

@orpc/mcp

npm i https://pkg.pr.new/@orpc/mcp@2302

@orpc/msw

npm i https://pkg.pr.new/@orpc/msw@2302

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2302

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2302

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2302

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2302

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2302

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2302

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2302

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2302

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2302

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2302

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2302

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2302

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2302

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2302

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2302

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2302

commit: bad09e3

@codspeed

codspeed Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/server-functions-useactionstate-6e8300 (bad09e3) with main (af4f625)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the full feat(next): let server functions plug into useActionState diff (6 files): the ServerFunction overload and runtime two-argument branch, the createServerFormFunction parser swap, the new runtime/type/jsdom tests, and the Next.js docs section.

  • ServerFunction gains the useActionState call shape — createServerFunction now branches on args.length > 1, discards args[0] as the previous state, and takes args[1] as input, deserializing FormData via parseFormData (packages/next/src/server-function.ts:82). I verified against the installed React 19.3.1 / Next 16.4.1 bundles that every useActionState path — client dispatch, programmatic dispatch(), and no-JS progressive enhancement — invokes the action with exactly (prevState, formData | payload), so the heuristic lands on the right argument in all three.
  • No $ACTION_* leakage into the input — in the no-JS path decodeAction filters every $ACTION_-prefixed field out before binding the cleaned FormData to the action, so strict schemas will not see React's bookkeeping keys.
  • createServerFormFunction now shares parseFormData — [...form] → parseFormData(form) is behavior-equivalent (Array.from(form.entries())), as the PR body states.
  • Tests — runtime coverage for the new shape and single-arg raw-FormData passthrough, a jsdom test driving real React useActionState, and type tests for the overload and inference. pnpm vitest run packages/next, pnpm --filter @orpc/next type:check, the JSDoc backlink check, and blume validate --strict all pass locally.

The two acknowledged behavior changes (the .bind slot reassignment and the added signature narrowing what satisfies ServerFunction) are deliberate, documented in the PR body and the docs warning, and consistent with main being the beta line.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

The jsdom useActionState test already proves the previous state is ignored
and both FormData and plain input reach the procedure, so the matching
unit and type tests are dropped. Its second dispatch is now a valid call,
so it fails if the previous state were used as input.

Co-Authored-By: Claude <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 23, 2026 06:11

Dismissing prior approval to re-evaluate 07e9221

Co-Authored-By: Claude <noreply@anthropic.com>
@dinwwwh
dinwwwh merged commit 3ff2342 into main Oct 23, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant