leptos
leptos copied to clipboard
Differing behaviour between dev and release compilations when updating/rendering RwSignal<Vec<(Fragment, i32)>>
Describe the bug Hello,
I am currently working on a demonstrative chatting client using leptos.
I have an RwSignal<Vec<(Fragment, i32)>> (Don't judge me), being updated via the following
val.push((
view!{cx,
<>
<div class="flex mt-2 gap-x-3 text-sm border-gray-300 rounded-md bg-sky-200 p-2 w-fit" id=item.id>
{link.inner_text()}
<Icon icon=IoIcon::IoClose class="h-3 w-3" on:click=move |_| {
input_signal.update(|val| {
let index = val.iter().position(|(_, id)| *id == link.value()).unwrap();
val.remove(index);
});
(!input_signal.get().iter().any(|(_, id)| *id == link.value())).then(|| {
input.set_value(&(input.value().replace(&(link.value().to_string() + ","), "")));
input.set_value(&(input.value().replace(&(link.value().to_string()), "")));
});
}/>
</div>
</>
},item.id)
And rendered here:
<input type="text" class=move || format!("w-full py-2 px-4 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
text-transparent select-none selection:bg-none {}", if disabled.get() {"opacity-50"} else {""})
placeholder="Select an option"
name="other_users"
_ref=_ref
on:click=move |_| hidden_state.update(|val| *val = !*val )/>
{move || input_signal.get().iter().map(|val| {
val.0.clone() // <--- Here
}).collect_view(cx)}
<ul class=move || format!("absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded-md shadow-lg {}", if hidden_state.get() {"hidden"} else {"block"})>
Leptos Dependencies
As requested:
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros", "secure-cookies"] }
actix-identity = { version = "0.5.2", optional = true }
actix-session = { version = "0.7.2", features = ["redis", "redis-rs-session"], optional = true }
console_error_panic_hook = "0.1"
cfg-if = "1"
leptos = { version = "0.3", default-features = false, features = [
"serde",
] }
leptos_meta = { version = "0.3", default-features = false }
leptos_actix = { version = "0.3", optional = true }
leptos_router = { version = "0.3", default-features = false }
wasm-bindgen = "0.2.87"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
getrandom = { version = "0.2.9", features = ["js"] }
serde_urlencoded = "0.7.1"
fancy-regex = { version = "0.11.0" }
validator = { version = "0.16.0", features = ["derive", "phone"] }
lazy_static = "1.4.0"
env_logger = "0.10.0"
web-sys = { version = "0.3.63", features = ["HtmlFormElement", "SubmitEvent", "KeyboardEvent", "Window", "Location", "History", "File", "FileList", "HtmlInputElement", "HtmlLiElement"] }
gloo-net = "0.2.6"
gloo-file = { version = "0.2.3", features = ["futures"] }
tokio = { version = "1.28.1", features = ["rt", "process"], optional = true }
wasm-bindgen-futures = "0.4.36"
sea-orm-migration = { version = "0.11.3"}
async-trait = "0.1.68"
sea-orm = { version = "0.11.3", features = ["sqlx-mysql", "runtime-tokio-native-tls", "with-chrono"], optional = true }
lettre = { version = "0.10.4", optional = true }
askama = "0.12.0"
base64 = "0.21.2"
chrono = "0.4.24"
rand = "0.8.5"
redis = "0.23.0"
argon2 = "0.5.0"
leptos_icons = { version = "0.0.12", default_features = false ,features = ["AiCloseCircleFilled", "HiChatBubbleOvalLeftEllipsisSolidMd", "HiUserCircleSolidMd", "HiArrowLeftCircleOutlineLg", "AiUserOutlined", "AiUserAddOutlined", "HiChevronLeftSolidLg", "BiUserCircleSolid", "HiEllipsisHorizontalSolidMd", "TbPhotoFilled", "HiPaperAirplaneOutlineLg", "LuImageOff", "IoClose", "IoTrash", "FiAlertTriangle"] }
futures-util = "0.3.28"
iter_tools = "0.1.4"
infer = "0.13.0"
To Reproduce Steps to reproduce the behavior:
- Clone the following link
- Run
docker build -t zing .
. This may take 10-15 minutes to build depending on your system. - Run
docker run -p 8000:8000 zing
- Create 3 accounts with valid email addresses as you will need to validate your email.
- Visit this link and click the group chat icon adjacent to the "messages" header.
- Select the other two users you have created and click on create. You will need to reload after this as I have not yet created an appropriate update signal.
Expected behavior The expected behavior is that within the first image (Dev build).
The unexpected behavior is that within the second image (release build).
Screenshots
Additional context
The component is the GroupChatModal
on line 1295 of the following link.
If I fix this issue, I will post the fix.
Thanks for this very detailed report!
The issue almost certainly has to do with the way you are cloning and reusing the fragment, I agree.
I'd recommend trying two things:
- Is it possible for this to be an
RwSignal<Vec<(HtmlElement<Div>, i32)>>
instead? i.e., to simply drop the wrapping<></>
fragment around the<div>
you are using? If so, this would likely work correctly. - It's worth checking whether this is an issue that's been fixed between 0.3 and the current main release by updating from
0.3
to a dependency on git main.
Otherwise, a minimal reproduction that doesn't require a 10-15 minute build process, creating user accounts, etc. to set up will make it more likely I'll get to this sooner.
Ah, I see.
Is there a difference between the implementation of Fragment and other view elements at compile?
Yes. Fragments are backed by a regular web DocumentFragment
, which are somewhat tricky to work with. (Although honestly, all DOM elements are hard to deal with). Appending a DocumentFragment
to the DOM somewhere moves its children out, so we have to gather them back in if you unmount it and move them somewhere else. It's possible there's an edge case that's missed here somewhere, and the discrepancy between debug and release mode suggests that's the case, but it's also just true that fragments are trickier than regular elements.
Ok,
I have found a solution, but it points to an issue within the intricacies within the implementations of collect_view(cx).
A slight modification to the initial implementation has been introduced from:
{ move ||
input_signal.get().iter().map(|val| {
val.0
})
.collect_view(cx)}
to:
<For
each=move || input_signal.get()
key=|input| input.1
view=move |cx, item: (HtmlElement<Div>, i32)| {
view! {
cx,
{item.0}
}}/>
seems to have fixed the issue.
Here's a simpler reproduction, for my own purposes in addressing this one:
#[component]
fn App(cx: Scope) -> impl IntoView {
let count = create_rw_signal(cx, 0);
let frags = create_rw_signal(cx, vec![]);
view! { cx,
<button on:click=move |_| {
count.update(|n| *n += 1);
frags.update(|f| f.push((
view! { cx, <><span>{count.get()}</span></> },
count.get()
)));
}>"+1"</button>
<div>
{move || frags.get().iter().map(|val| {
val.0.clone() // <--- Here
}).collect_view(cx)}
</div>
}
}
No longer relevant in 0.7