- Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
With any HTML-rendering web tech, people use PRG for several reasons:
- Because after an operation has completed, you need to move the user to a totally different page
- Because after an operation has completed, you want to keep the user on the same page, but:
A. ... you want the top item on the history stack to be a GET so that if they press "reload", they don't duplicate the operation
B. ... you want to restart the whole page generation process so that unrelated components will display the new state following the operation, not the old state
For scenario 1, everything works fine on all web tech, both Blazor SSR and everything else.
For scenario 2, it's bad on all web tech, both Blazor SSR and everything else. Doing a PRG to the same URL (possibly repeatedly, e.g., when repeatedly adding the same item to your cart) stacks up repeated copies of the same URL on the browser's history stack. This wrecks the "back/forward" experience. People want to solve this but can't. It's so lame I think we should say PRG is an antipattern if you're not changing the URL, and you should not do it.
With Blazor SSR, I'm not too bothered about scenario 2A, because as long as you enabled enhanced form posting, no problem occurs. No POST request goes onto the history stack, so it remains safe for the user to refresh. That is, for 2A, enhanced forms mean you simply don't need to use PRG, and therefore don't have to wreck the history stack.
The issue we should consider fixing
This issue is really about scenario 2B. With Blazor SSR, it's actually really valuable to have a way of restarting the whole rendering process after completing an operation. Because otherwise, different parts of your UI could be out of sync. Consider this actual real example I was dealing with this morning:
- On an eCommerce site, there's a header bar that displays the number of items in your cart
- On some page
ProductDetails.razor, you can add a product to your cart via a form post. However, this is an async operation so by the time it's done, the header has already rendered, and is showing the old number of items in your cart, not the new number.
To fix this, I had to go through a massive detour of adding a stateful service with a SubscribeForCartChangeNotifications and have the Header.razor component register itself and unregister on dispose, and re-render itself when the cart contents change. And then there was another bug when I enabled streaming SSR on ProductDetails.razor - doing that meant I had to also change Header.razor to be streaming as well, otherwise it still wouldn't update.
Basically, I had to deal with all the complexities you'd face synchronizing unrelated components in interactive rendering, even though with traditional SSR people don't expect to have to deal with all that.
Proposal
To simplify SSR, we should support a better version of PRG. That is, we could change the blazor.web.js logic that deals with redirections after an enhanced form post, and if it's a redirection to the URL you're already at, we simply don't push another history entry.
The payoff would be that the most basic and obvious way of doing PRG would just start working - it would no longer mess up the history stack, and all the nasty manual subscriptions/unsubscriptions for state changes would become unnecessary.
The cost would be that it's no longer exactly equivalent to the effect of a non-enhanced form post, which violates the main design intent of enhanced nav (being consistent with non-enhanced nav). However in this specific case I think it would be a desirable choice. It would only be a one-line change in the framework, and would avoid so much application code complexity.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status