-
|
The topic around Resources and web sockets has been a difficult implementation detail and I thought I had come to a working solution - but some recent update has started causing errors. "Uncaught Error: closure invoked recursively or after being dropped" I've been able to narrow this down to a specific use case of using I suspect thaw has some sort of closure state that goes stale when the component re-renders after the Resource has been updated - and what I'm hoping would solve it is to not re-render the entire component when the Resource is updated and instead have more fine-grained reactivity that is normally afforded to signals. The reason for this specific architecture is to have a complete SSR page, that hydrates and continues to update with server data for displaying server-side metrics. Below is an example using the websockets example from leptos. I suspect this is just a bad pattern - but I have not been able to figure out how an alternative. My past attempts tried to pipe a Resource into a Signal and encountered other issues at the time. The goal here would be to prevent the entire Popover component from re-rendering whenever the Resource is updated and instead just have the specific text-nodes update. For this little demo - it's easily solved just be removing the extra mapping closure that gets the resource - but in my real application, of course, it's much more complicated with larger data structures and nested components with context and props that take #[server(protocol = Websocket<JsonEncoding, JsonEncoding>)]
async fn tick_ws(
_input: BoxedStream<u32, ServerFnError>,
) -> Result<BoxedStream<u32, ServerFnError>, ServerFnError> {
use futures::{SinkExt, channel::mpsc};
use std::time::Duration;
let (mut tx, rx) = mpsc::channel(1);
tokio::spawn(async move {
let mut x = 1;
loop {
let _ = tx.send(Ok(x)).await;
x += 1;
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
Ok(rx.into())
}
#[component]
pub fn App() -> impl IntoView {
let latest = Resource::new(|| (), async |_| "x: 0".to_string());
if cfg!(feature = "hydrate") {
spawn(async move {
let (_, rx) = mpsc::channel(1);
match tick_ws(rx.into()).await {
Ok(mut stream) => loop {
while let Some(Ok(x)) = stream.next().await {
latest.set(Some(format!("x: {x}")));
}
},
Err(e) => leptos::logging::warn!("{e}"),
}
});
}
view! {
<ConfigProvider>
<Transition>
{move || {
latest
.get()
.map(|latest| {
let latest_x = Signal::stored(latest);
view! {
<Popover>
<PopoverTrigger slot>
<div>"hover me"</div>
</PopoverTrigger>
{latest_x}
</Popover>
}
})
}}
</Transition>
</ConfigProvider>
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 9 replies
-
|
Honestly I don't think I'd use a I'm not familiar enough with thaw to know what would cause the issue. If you can create a non-thaw reproduction, feel free to open an issue here and I'm happy to look at it. |
Beta Was this translation helpful? Give feedback.
Fair enough.
The
Storeidea was really just a way to get fine-grained access to the individual fields of a deeply-nested type.You can do the same thing by putting it into a signal and deriving memos for the subfields, or by just accessing the resource (and, similarly, creating memos around the fields). Personally, I think the simplest solution (if the updates are coming primarily over a websocket) is to wait for it once in Suspense, stick the whole thing into a signal and then access the fields as memos. Rather than creating 30 different Suspense nodes. (This is really just a question of how many individual chunks you want to stream in from the server during SSR — but of course adding Su…