DEV Community

Cover image for React 19 Actions: I Explained 3 Hooks Without Ever Explaining What an Action Is

React 19 Actions: I Explained 3 Hooks Without Ever Explaining What an Action Is

Shubhra Pokhariya on September 01, 2026

Three parts into this series, and if you'd asked me to define the word sitting underneath every single hook I'd taught you, I'm not sure I could ha...
Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

So, "action" is the mother and the four "use" brothers. This is so complicated. Nice catch as always! πŸ˜„

Picked as gem
Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks! 😊 That family analogy actually fits surprisingly well. The fun part is seeing how these pieces start making more sense once you see the Action model underneath them.

Collapse
 
ofri-peretz profile image
Ofri Peretz

The note that useOptimistic is the odd one out β€” a setter you call from inside an Action rather than a doorway into becoming one β€” is the kind of invariant that almost can't be expressed in a type signature, so it lives entirely in docs and reviewer intuition until someone calls it from the wrong scope and gets silent misbehavior. I've been running into the structural version of this problem in static analysis: ESLint rules that need to flag async patterns have to reason about call sites, not just function bodies, and that's exactly the same challenge you're describing β€” the function is neutral, the context is what promotes it. The four-doorway framing makes that explicit in a way I hadn't seen stated cleanly before. Wondering whether you think a lint rule could realistically enforce "don't call useOptimistic outside a Transition" or whether that's always going to be runtime territory.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks, Ofri. That's a really precise way to put it: the function is neutral, the context is what promotes it.

On the lint rule, I think you could catch some of the obvious misuse, but probably not enforce the invariant completely. A rule can reason about where the optimistic setter is called and flag cases where there's clearly no Transition context. The harder cases are when that context is indirect or the setter is passed through another function. At that point, the rule has to reason about the call chain and control flow rather than just the function body.

So I'd see linting as a useful guardrail, not the source of truth. useOptimistic doesn't create the Action context itself. Its setter assumes you're already inside one, which is a runtime relationship that isn't really expressible in the type signature.

Collapse
 
merbayerp profile image
Mustafa ERBAY

Great breakdown. The ordering section especially caught my attention.

I’ve been working on form/POST semantics in a deterministic application runtime, and it made me think about where the responsibility for an β€œAction” should end.

pending, optimistic updates, and disabling submission are great UI concerns, but they don’t guarantee correctness once the request crosses the server boundary. Duplicate submissions, retries, concurrent requests, and out-of-order execution still need idempotency, transaction and concurrency semantics on the backend.

So I’ve started thinking of the form as just an adapter to an Action rather than the owner of it. The same Action should ideally be callable from a form, API, job, CLI, etc., while keeping the same validation and execution guarantees.

Your explanation of Actions as the shared mechanism underneath these APIs made that separation much clearer. Great article.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks, Mustafa. I think that UI/server boundary is exactly the interesting part here.

Pending, optimistic updates, and disabling submission can make the UI behave correctly while an operation is in flight, but they don't make the operation itself safe against retries, duplicate submissions, or concurrent execution. Those guarantees have to come from the server-side operation and its contract.

And I like your point about treating the form as an adapter. Once you separate the React coordination from the underlying operation, the same operation can be reached from a form, API, job, or CLI without making those callers responsible for its correctness.

That's probably the boundary your comment makes especially clear: React can coordinate the UI around an Action, but it can't define what "correctly executing this operation twice" means. Whether duplicate execution is safe, rejected, or needs to be serialized is part of the server-side contract.

Collapse
 
merbayerp profile image
Mustafa ERBAY

I like the distinction between the React Action and the underlying operation. β€œOperation” is definitely the cleaner term here.

It keeps the boundary clear: React coordinates the interaction, while the operation owns correctness.

That separation becomes especially valuable once the same operation has multiple entry points. Thanks for sharpening that distinction.

Thread Thread
 
shubhradev profile image
Shubhra Pokhariya

Exactly. That distinction became much clearer to me once I separated the React-facing coordination from the operation itself. β€œOperation” also feels like the better term because it doesn't tie the underlying work to any particular entry point. Glad that framing resonated.

Collapse
 
leob profile image
leob

"The same Action should ideally be callable from a form, API, job, CLI, etc" - I think that hits the nail on the head - it shouldn't matter what kind of "client" invokes the backend - the end result should be the same ...

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Exactly, Leob. I think the important distinction is that the business operation should stay the same regardless of who invokes it, while the React-facing Action can be the adapter around that operation.

Even within React, the calling convention changes a bit. A form gives the Action FormData, while useActionState adds the previous state as the first argument. So I'd keep the actual business operation underneath that boundary and let the Action handle the React-specific coordination.

That's probably the next layer I only hinted at in the post: the Action can be a thin adapter around the operation, rather than making the Action itself the business-logic boundary.

Collapse
 
99tools profile image
99Tools

This was a really helpful way to tie everything together. I especially liked the explanation of why useTransition doesn’t handle ordering the same way useActionState and form Actions do. It makes React 19 Actions much easier to understand as one concept instead of a bunch of unrelated hooks.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks so much! 😊 I’m really glad that came through. The ordering distinction with useTransition was important to include because the APIs can share the same Action model without providing the same guarantees. Once that clicks, the four stop looking like unrelated hooks and start feeling like one thing.

Collapse
 
leob profile image
leob

Very well explained, and didactically (first lay out the problems and let the reader fully "grok" them - only later on the answers are laid out in full) this is probably the right way to structure it ...

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks, Leob! ☺️ That was exactly the bet with this one. I wanted readers to feel the problems first and only then put a name to the thing underneath them. By the time I finally got to β€œAction,” the difference between what useActionState gives you and what useTransition gives you should already feel pretty obvious. Glad that came through.

Collapse
 
hemapriya_kanagala profile image
Hemapriya Kanagala

Shubhra, I loved how you explained the four hooks as different answers to different problems. It made it much easier for me to understand why they are not really competing with each other πŸ˜€

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thank you so much, Hemapriya! 😊 That was exactly what I wanted to make clear with this post. useActionState and useTransition, for example, are solving different problems even though they’re both using the same Action mechanism. Once you see that, the four APIs stop feeling like competing hooks and start making a lot more sense together.