-
-
Notifications
You must be signed in to change notification settings - Fork 633
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
[0.7] Suspend inner runs twice, causing double-render of returned View #2956
Comments
AFAICT this is working as designed. In order to register both synchronous and async Resource reads, When rendering, the In the example you provide,
As a result, the If the server function is actually async on the server (i.e., it is not immediately resolved when called) and the side effect is after the #[server]
pub async fn get_thing(thing: ()) -> Result<(), ServerFnError> {
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
Ok(thing)
}
// ...
Suspend::new(async move {
let _ = resource.await;
count.update_untracked(|x| *x += 1); Another workaround here is not using I'm happy to try to help debug something closer to your real example with the websockets if needed. I don't think that the behavior will change here, as it's pretty fundamental to the async/sync Suspense system, but if there's an example that isn't working as intended I'm happy to fix it. |
Since this was something I've observed while building the test case for #2937 I took the liberty to test this again with the additional modification to the same gist I've used as the test bed, the code after await will still be called twice under CSR as the first request will be held up by the unresolved future. I've used logging instead to more clearly illustrate the flow that the futures have taken. The relevant logging/action parts of closure that returns the view! {
<Suspense>
{move || {
leptos::logging::log!("inspect_view closure invoked");
Suspend::new(async move {
leptos::logging::log!("inspect_view Suspend::new() called");
let result = res_inspect.await.map(|item| {
leptos::logging::log!("inspect_view res_inspect awaited");
view! { ... }
});
count.update_untracked(|x| *x += 1);
leptos::logging::log!(
"returning result, result.is_ok() = {}, count = {}",
result.is_ok(),
count.get(),
);
result
})
}}
</Suspense>
} (Edit: The resource itself actually has two Where
While reloading that to get the SSR rendering, we get this instead (logged onto the server's console):
Plus the log related to hydration (logged to the browser's console):
Edit - Moreover, I figured I should also visit this point:
I did something like the following: let just_the_suspend = Suspend::new(async move {
let result = res_overview.await;
count.update_untracked(|x| *x += 1);
leptos::logging::log!("res_overview.is_ok() = {}; count = {}", result.is_ok(), count.get());
});
view! {
<h5>"<ItemInspect/>"</h5>
{just_the_suspend}
<Suspense>
{inspect_view}
</Suspense>
} Which does work as expected:
Note that wrapping the Now that I had all the time to work through this example again and spent a lot more time thinking about how this all works, I think I am starting to get that why the difference of behavior - the final rendering of SSR + hydration is effectively has the identical number of passes as CSR. Note that the final two lines of each of the SSR and hydrate logs are identical, and just simply done twice one after the other for CSR, which just shows how this is functioning as designed. Would be useful to document this exact behavior if this is really the case (specifically, the instances where an asynchronous function is needed but no reactivity is required that the bare |
@metatoaster This behavior (running 2x on the client) is the opposite of the example in the original issue (running 2x on the server), but sounds more like the bug behavior described (creating websockets 2x). I hadn't fully thought about this one, so thanks to you both. What's happening here is:
I'll have to think a bit about how to handle this: We definitely do want that |
The original issue included showing the server side double-effect just because I noticed it after the fact. In the video, from my issue, you can see the count always going up twice when client-side routing. The real issue for me was always 2x on the client (websockets only created in component on the client).
On the server side, this is true - the result is what I would expect. But on the client side, everything after the .await is still run twice - and I'd argue this is a bigger deal because:
|
Yes, I just misunderstood the original issue and this is obviously bad. |
FYI - I updated the example branch to include leptos-double-websocket.mp4 |
I'm starting to feel pretty good about this whole #2959 should actually fix this issue: It creates a new reactive observer, which catches all the reactive async reads in the But let me know! I also want to take the opportunity to introduce a cancellation mechanism here, which was an existing |
I will test this tonight and report back. I'm impressed by your turnaround on these issues! 🙌 |
This comment was marked as resolved.
This comment was marked as resolved.
Actually, there may be a different problem to what I described as I think it may be hydration related and the gist I've provided does demonstrate this issue. The delay of From the Also, I've observed the multiple retrieval under certain conditions, which can be triggered by refreshing and navigating to certain routes. Not sure if this one is actually push-state related, Use the top nav bar (use the accompanied |
Branch works on my end to resolve the issues I've had. |
@metatoaster The PR broke reactivity during the hydration of Suspend (so, refreshing on the page), which CI and you both caught. I think this covers the first issue and half of the second issue. I would suggest opening the second half of the second issue (with the complex series of navigations) as a second issue, so that this one can be closed. I am not sure where in the complex chain of events this second server function invocation is coming from, given the routing structure, wildcard segment, it only happens when using the "forward" button but not navigating with the link, etc. |
This comment was marked as outdated.
This comment was marked as outdated.
fix: do not retrigger parent effect when Suspend's resources resolve (closes #2956)
Describe the bug
When a Suspend is created to await a resource, the inner future is run twice. This results in the returned Views rendering twice.
In my case, this was leading to duplicated websocket connections, probably because of a race condition between web_sys callbacks and Leptos cleanup.
Leptos Dependencies
0.7 (only tested on
-beta5
andfix-refetch
)To Reproduce
See recreation in this start-axum-0.7 fork branch:
https://github.com/BakerNet/start-axum-0.7/tree/demo/suspend-bug
https://github.com/BakerNet/start-axum-0.7/blob/demo/suspend-bug/src/app.rs#L75
Expected behavior
The Suspend inner only runs once inside a Suspense/Transition block unless refetched or source data changes
Screenshots
leptos-suspend-double-render.mp4
The text was updated successfully, but these errors were encountered: