-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Actions #912
Actions #912
Conversation
This reverts commit 62fd2bb.
Does this require View Transitions to be enabled like for form data processing? |
I want every bit of this for StudioCMS.... |
@rishi-raj-jain Not at all! We are targeting client components to show how JS state management could work. But forms are callable from an |
@bholmesdev Will this be available for SSG at all, or is it just Hybrid/SSR? |
@jdtjenkins Ah, good thing to note. Since actions allow you define backend handlers, you'll need a server adapter with |
My turn to bikeshed you. |
@ascorbic That's fair! I agree that's a better name, happy to use it. |
@ascorbic Thinking it over, went with |
Hey @bholmesdev, it's me back again to make sweeping criticisms about one of your big proposals again 😁 (I'm the one who got you to vendor Zod back when you were working on content collections). Also, sorry for being so late to submit on this - I only just found out about this proposal from the 4.8 release! Reading through the proposal, I noticed almost every example uses the Here's the single example of how to implement actions without JS: import { actions, getActionProps } from 'astro:actions';
export function Comment({ postId }: { postId: string }) {
return (
<form method="POST" onSubmit={...}>
<input {...getActionProps(actions.comment)} />
{/* result:
<input
type="hidden"
name="_astroAction"
value="/_actions/comment" />
*/}
</form>
)
} It seems like a bit of an afterthought, to be honest, which doesn't really fit with Astro's whole 'Ship less JS' thing, and I hope we can all agree that My understanding is that under the hood, Astro sees the E.g. import { actions, getActionURL } from 'astro:actions';
export function Comment({ postId }: { postId: string }) {
return (
<form method="POST" action={getActionURL(actions.comment)} onSubmit={...}>
{/* Regular form goes here */}
</form>
)
} Some people wanted something similar to this that involved directly importing actions with import attributes, but it seems like that wasn't viable. This seems like a nice middle ground, where you still (kinda) pass the handler to the Also, the rest of the proposal looks great. I can't wait to have a play with some Actions! |
@zadeviggers Form support was not an afterthought, it was the biggest part of the design and why the feature didn't ship sooner. An API like the one you're suggesting was considered but it's incomplete. What happens on the server-side after an action is executed? How do you redirect to a different page? How do you handle validation errors? @bholmesdev went through a few different designs to try and make all of those things possible but it came at the cost of making the action code much more complex. The That said, this RFC is still open so we can talk about other approaches. |
Thanks for the overview @matthewp. Actually, I would not consider the This isn't perfect though. The main reason for a hidden input over this was challenges with React 19. When using actions in React, they own the <form action={actions.like}> But now, how can we add a fallback? React stubs |
@bholmesdev I'm not sure I understand. If the |
@matthewp Well I should clarify: the action would NOT be a URL. It would be a query param to re-request the current page. <form action={actions.like.url}>
<!--output: ?_actionName=like--> This does the same work as passing a hidden |
Oh ok, I understand now. I still like So I think this trades away flexibility for a subjective aesthetic gain only. |
@matthewp To address your flexibility concern, we can add an optional parameter to the function to specify a different path for it to submit to. I don't think this will come up super often, though, since people will probably just do all their handling in the action, and then rewrite to the page they want to render. @bholmesdev I really like the |
Now I read the actual code for export function isInputError<T extends ErrorInferenceObject>(
error?: ActionError<T>
): error is ActionInputError<T> {
return error instanceof ActionInputError;
} That's it! Not the safest of types but I'll take it and it also means its trivial to make it accept a broader type as input. I can confirm that client-side const { isPending, mutateAsync } = useMutation(
{
mutationFn: actions.products.update,
onSuccess: (_data) => {
rqc.invalidateQueries({ queryKey: KEYS.products.all() })
onSaveSuccess?.()
},
onError: (error) => {
// the `error` is typed "Error" which is incompatible with `isInputError`
// confirming that this check returns `true` from client-side code
console.log(`there was an error and it is an actioninputerror: ${error instanceof ActionInputError}`)
},
},
queryClient,
} Given that the type guard is so trivial I would lean towards nixing the type guard entirely and just documenting the Its like |
It seems the cookies issue is not restricted to the actions context as a similar post has been made on Discord. |
Thanks for following up @wesleycoder! In hindsight, I should've suggested an issue instead of tracking myself. I knew I would be stepping away from actions for the following few weeks, sorry about that. Since I said I'd take a look, my goal is to wake up tomorrow, get a cup of coffee, and try to PR a fix. |
Haven't missed your comments @firxworx! All valid concerns here. There's some nuance to the API choices I could detail, but I'm not 100% sure on them. I'll take a second look to see what we can improve. |
Missed your note on serializing complex objects @ernestoalejo. You're correct that we only allow JSON serializable inputs, though we don't document this in the RFC. We avoided superjson to keep the client-side of actions as lean as possible. Alternatives like devalue or seroval may offer the same utility in a smaller footprint. I agree dates are fairly common to send over the wire. Do you have a use case where this should be configurable by you (i.e. editing serialization options yourself)? Or would a global default that covers Dates be sufficient? |
In general I have found that TypeScript libraries that provide a smooth DX and avoid manufacturing headaches/issues generally export pretty much all of their types + interfaces even ones that seem internal or its hard to think of a use-case. Especially with actions being new, to enable the community to work with them in ways that perhaps weren't foreseen and have low friction to building helpers + utilities without resorting to fake/wrong types with Example use-case: I thought perhaps I'd make a generic wrapper for handlers e.g. I'm not blocked per se but I was surprised to find some of the potentially relevant types aren't exported. |
I won't keep posting since you got the general spiel already above however this time I have something to add: Beyond exporting it, I suggest as a general philosophy for the majority of cases when you have a type like
And when you define the type: This opens up worlds of magic for downstream devs who now can write helpers and use the validation libraries of their choice (e.g. zod's For fun -- example of what we presumably want to avoid pushing on devs: import { ActionError } from 'astro:actions'
type ConstructorParameters<T> = T extends new (...args: infer P) => unknown ? P : never
type ActionErrorConstructorParams = ConstructorParameters<typeof ActionError>
type ActionErrorCode = ActionErrorConstructorParams[0]['code'] |
Good point on utility exports @firxworx. There is a method to a madness: exposing internal utilities means an earlier maintenance burden. We could exposing all of our internals for library authors, but this subjects them to documentation, versioning, and caution with breaking changes. We prefer a more cautious route to only expose utilities when there is a strong user need, like what you've presented here. Happy to expose an Update: PR is up |
PR is now up to expand |
Thanks for the feedback everyone! I've compiled all the notes from @jdtjenkins @wesleycoder @ernestoalejo @chiubaca @Southpaw1496 @mayank99 @zadeviggers. Now, we're nearing a stable release. I've put up a few discussions in the
Join the discord and give us feedback! Excited to shape version one with you all. |
Big news! We've reached consensus on those API choices above.
We also introduced a change to remove async local storage from the Actions internals. This means Cloudflare and Stackblitz work out of the box, no build config needed. Now, we're leaving 2 more weeks as experimental to get final thoughts on these changes. If all goes well, we expect Actions to be baselined as stable in the next minor (4.14.0) two weeks from now. Before then, I'm opening a call for consensus on this RFC. If you have thoughts, please try the latest release and leave feedback here. If there is no contention, my goal is to close this RFC as accepted next Tuesday. cc to our active contributors: @jdtjenkins @wesleycoder @ernestoalejo @chiubaca @Southpaw1496 @mayank99 @zadeviggers |
--- | ||
import { actions } from 'astro:actions'; | ||
|
||
const result = await Astro.callAction(actions.searchPosts, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would feel more natural to call action.searchPosts()
directly on the server too, similar to how it's done on the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this was removed recently due to technical limitations of injecting APIContext
. This did work in an earlier version, but it was removed due to environment issues with async local storage. I agree it's a compromise, but we unfortunately don't have alternatives today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's a bit unfortunate. Maybe one day!
This is a very minor issue anyway, since the big problem/use-case that actions solve is calling backend code from frontend.
(Feel free to resolve this thread; I can't seem to do it myself)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mayank99 Yeah, that was my thinking. I agree that "just call it" is the dream! There's an avenue using compiler hacks that I dreamt up, but the maintenance burden was hard to justify. Open to any JS heroes that can find a path.
In the current release 4.13.1 the types are broken for using I discovered the issue when upgrading from 4.11.1 and revising code to accommodate Representative ExampleConsider the following example with const x = useQuery({
queryKey: QK.widgets.all(),
queryFn: actions.widgets.getAll,
}) If I revise the query function line to be However... the types lie! If I revise to the following and force it to compile... queryFn: actions.patients.getAll.orThrow, ...the code works: the browser sends the expected request body and I get the expected response back from the server. WorkaroundsAdding A hacky workaround is adding
Interesting ObservationsFunnily enough if add the Use CasesThe use-case where The use-case for having a "GET"-like action without an Admittedly without reviewing the current code it makes me wonder if the underlying code is not type safe due to type cheats / escape hatches ( |
Alright, decided to keep us in experiment for one more release. We snuck in a request change: automatic redirects to avoid the "confirm post resubmission" dialog. You can try it out in the latest patch! As part of this, I also overhauled the instructions for using Actions with plain HTML forms. We better explain how redirects are handled, and include some advice for success and error banners. You can find this on the RFC or the latest documentation draft: Share your feedback and bug reports! If we're happy with this, we should be good for stable. |
Alright y'all, it's happening. Actions is scheduled to ship stable in 4.15 🥳 There are a few pieces we didn't manage to finish, but they're still on the list. Namely:
With that, I'm merging this RFC as accepted. Keep sharing bugs and feedback as always! Thanks everyone. |
Summary
Astro actions make it easy to define and call backend functions with type-safety.
Links