Skip to content
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

ActionForms get stuck on page of server function when JS and WASM disabled #1891

Closed
SleeplessOne1917 opened this issue Oct 13, 2023 · 8 comments

Comments

@SleeplessOne1917
Copy link
Contributor

Describe the bug
After some more testing on the issue I ran into for #1878, I am 95% certain that I discovered a bug.

Leptos Dependencies
Better than a list of dependencies, I made a minimal reproducible example.

To Reproduce
Steps to reproduce the behavior:

  1. Disable JS and WASM in your browser
  2. Run cargo leptos watch for the included example
  3. Click the "Do Something" button on the home page
  4. Notice that your browser will get stuck at the URL of the server function

Expected behavior
I get redirected back to the page where I submitted the form.

@gbj
Copy link
Collaborator

gbj commented Oct 13, 2023

This is working as designed. As I understand it, the expected behavior you describe in the case of an Err case without JS/WASM is that the form submission fails silently (i.e., redirects back to the page you sent the form from) and there is no way to access the error. The actual behavior is that the form lands on a page with an error message and an appropriate error code. Do you see what I mean?

I'm open to suggestions on how to make this better, of course; for example, we could adopt some kind of default pattern in which an Err results in redirecting back to the same page with ?server_fn_error={serialized error} or something, and then providing some way of dealing with that.

Alternatively, you can handle it by redirecting (using redirect()) in the case of an error in the server function.

@SleeplessOne1917
Copy link
Contributor Author

Alternatively, you can handle it by redirecting (using redirect()) in the case of an error in the server function.

I tried that and I still have the same problem. It may be because I am still returning an Err though.

As I understand it, the expected behavior you describe in the case of an Err case without JS/WASM is that the form submission fails silently (i.e., redirects back to the page you sent the form from) and there is no way to access the error.

Yes with the exception of what I emphasized in the quote. I expect the error would be handled during SSR.

The actual behavior is that the form lands on a page with an error message and an appropriate error code. Do you see what I mean?

I see. While I can understand wanting to return the correct status code to the browser, I would think that the errors should be handled in either in an error boundary defined by the user or inside the component with the form using the ActionForm's error prop to conditionally render an error message. Since the URL of the error page is the URL of the server function, I don't think the error can be caught in any error boundaries, and the actual message shown on the page is not user friendly.

Judging by your first suggestion, it seems that serializing the error in a way that can be used without JS/WASM is necessary for this sort of behavior. I wonder if the error information could be omitted from the URL entirely and just have the most recent serialized response be handled before the page is returned to the user?

@gbj
Copy link
Collaborator

gbj commented Oct 13, 2023

As I understand it, the expected behavior you describe in the case of an Err case without JS/WASM is that the form submission fails silently (i.e., redirects back to the page you sent the form from) and there is no way to access the error.

Yes with the exception of what I emphasized in the quote. I expect the error would be handled during SSR.

ActionForm in this case is posting to the server function endpoint, not to a page that is SSRed, though. So if you want to redirect back to the referrer and have the error reflected in SSR, you need some way to pass the fact that there was an error back to that page; which was the reasoning behind my suggestion to pass it in the URL.

I see. While I can understand wanting to return the correct status code to the browser, I would think that the errors should be handled in either in an error boundary defined by the user or inside the component with the form using the ActionForm's error prop to conditionally render an error message. Since the URL of the error page is the URL of the server function, I don't think the error can be caught in any error boundaries, and the actual message shown on the page is not user friendly.

ActionForm works by posting a form to the server function endpoint, so there is no SSR happening, no error boundary, and no component.

It's possible to create a version that does post to an SSRed page, and I explored that a little in #1120 but there are some limitations there.

I wonder if the error information could be omitted from the URL entirely and just have the most recent serialized response be handled before the page is returned to the user?

I'm sorry, I don't think I understand what this means on a practical/technical level.


My impression is that on each of these points the core issue is that ActionForm doesn't work quite the way you are expecting it to work. Server functions define POST endpoints, and ActionForm works by POSTing to that endpoint.

  • With WASM it works fine and not much further discussion is needed.
  • Without WASM it POSTs to the endpoint
    • if it's an Ok(_) value returned from the server fn, redirects back to the URL in the Referer header.
    • if it's an Err(_) value, displays it
  • It does not make a request to a renderable route in your app, and the server function could be called from any one of several different pages or from none, so it can't really assume it's a renderable page.

@SleeplessOne1917
Copy link
Contributor Author

It does not make a request to a renderable route in your app, and the server function could be called from any one of several different pages or from none, so it can't really assume it's a renderable page.

Maybe server actions could implicitly send the route they're being called from when submitted by an ActionForm? That way the server functions can redirect on failure if the from route is defined. Also, apologies if I'm having trouble communicating clearly. The idea of how to accomplish this is pretty fuzzy in my head still.

@gbj
Copy link
Collaborator

gbj commented Oct 14, 2023

Maybe server actions could implicitly send the route they're being called from when submitted by an ActionForm? That way the server functions can redirect on failure if the from route is defined.

Hm. Yes so the headers do already include the URL of the page it's called from, which is why it manages to redirect successfully back to that page on Ok(_). If it redirected back on Err(_) that would just silently eat the error. There needs to be some way of including that error information as part of the new request to redirect back to the page.

Including it in the URL for the redirect is messy but possible. I wonder if there's actually a way to include a header, and have it passed through to the new request. i.e., to redirect back from the server function we send a 303 See Other. I wonder if there's a way to set a header X-Leptos-ActionForm-Error or whatever, and have that included in the browser's new request. Hm. To be honest I don't know enough about HTTP to know if there's a way to send a header back in our response, and have the browser include the header in its request to the new page. Unless... uh I guess we could set a cookie?

ActionForm could be told that in SSR mode, it should check for this error and try to deserialize it as an Err(ServerFnError) and use it as the error signal's value in which case the SSR-only form would work just like the hydrated one.

Also, apologies if I'm having trouble communicating clearly. The idea of how to accomplish this is pretty fuzzy in my head still.

No worries at all! It's fuzzy in my head too, and I wish it worked a little better!

@pintoflager
Copy link

If one uses <ActionForm> in a project where experimental-islands is enabled this is the expected behavior as well?

I was struggling to understand what's happening when I could only get error responses back as plain text from the #[server] function. Meaning that I couldn't read the responses from

let value = server_fn_called.value();

and trying to capture errors with <ErrorBoundary> also failed.
Then it hit me that if <ActionForm> is inside #[component] there's no registered

ev.prevent_default()

event handler either (components in islands mode only render html?) and the expected result is normal html form behavior - as in - off you go request and server responds as it pleases (In my case 500 / error text / no redirect) , right?

By the way if I try to add my <ActionForm> inside an island the browser console lights up like a christmas tree. Should it do that?

@gbj
Copy link
Collaborator

gbj commented Oct 29, 2023

If one uses <ActionForm> in a project where experimental-islands is enabled this is the expected behavior as well?

Yes, right! <ActionForm> outside an island behavior = <ActionForm> without JS/WASM behavior, so everything in this thread should be identical in those two cases.

I was struggling to understand what's happening when I could only get error responses back as plain text from the #[server] function. Meaning that I couldn't read the responses from

let value = server_fn_called.value();

and trying to capture errors with <ErrorBoundary> also failed. Then it hit me that if <ActionForm> is inside #[component] there's no registered

ev.prevent_default()

event handler either (components in islands mode only render html?) and the expected result is normal html form behavior - as in - off you go request and server responds as it pleases (In my case 500 / error text / no redirect) , right?

Yeah, sorry for the pain there. Glad you found your way to this issue for the explanation; your understanding is correct.

By the way if I try to add my <ActionForm> inside an island the browser console lights up like a christmas tree. Should it do that?

I think in the latest version (0.5.2) <ActionForm> should work inside an island (you can see it working on leptos.dev, for example!) but feel free to open a separate issue if that's not the case.

@gbj
Copy link
Collaborator

gbj commented Jan 20, 2024

Added in #2158.

@gbj gbj closed this as completed Jan 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants