dioxus
dioxus copied to clipboard
Submit buttons don't work on windows
On Windows, buttons with type=submit sometimes do not submit the form they are in. From debugging the dioxus source code I have narrowed it down to the following lines:
https://github.com/DioxusLabs/dioxus/blob/9e09bcf3f79aa7b349da23ca80cb2a214230562b/packages/interpreter/src/ts/native.ts#L340-L342
This just blatantly cancels any onclick events from submit buttons inside all forms. However, if there are no elements on the page with an onclick handler attached, the toplevel main div that dioxus implicitly inserts does not get an onclick event handler attached to it, which causes event.type to be click so handleClickNavigate does not get called in the first place:
https://github.com/DioxusLabs/dioxus/blob/9e09bcf3f79aa7b349da23ca80cb2a214230562b/packages/interpreter/src/ts/native.ts#L294-L296
I haven't bothered to check why this issue is not present on other platforms but to me this seems like a logic error. From my (somewhat limited) understanding the code in question is used to cancel onsubmit events from navigation buttons in case they happen to be inside a form (very weird/rare edge case, btw), but that whole issue can be sidestepped if those buttons just get their type set to button so onsubmit events won't be emitted in the first place.
- Dioxus version: 0.6.3
- Rust version: 1.85
- OS info: Windows 10
- App platform: Desktop
Perhaps other platforms also have this issue.
Actually, open further inspection, it seems that the navigator buttons cancel their default events anyway, so we should be good? https://github.com/DioxusLabs/dioxus/blob/9e09bcf3f79aa7b349da23ca80cb2a214230562b/packages/router/src/components/history_buttons.rs#L78-L81
Does this mean we can just remove
https://github.com/DioxusLabs/dioxus/blob/6bdb69e23529e0fd9813f535b7e4d957cc1d9b28/packages/interpreter/src/ts/native.ts#L337-L342
and be good? @ealmloff please give your opinion on this, I think #3791 was caused by the form submission event and not the button onclick event (at least not directly, from what I understand the latter triggers the former), but since form submission events should be blocked anyway, can we conclude that #3791 had another cause?
Does this mean we can just remove
dioxus/packages/interpreter/src/ts/native.ts
Lines 337 to 342 in 6bdb69e
// If the target is a form prevent the click event // from submitting the form let form = target.closest("form"); if (target.tagName === "BUTTON" && (event.type == "submit" || form)) { event.preventDefault(); } and be good? @ealmloff please give your opinion on this, I think #3791 was caused by the form submission event and not the button onclick event (at least not directly, from what I understand the latter triggers the former), but since form submission events should be blocked anyway, can we conclude that #3791 had another cause?
I don't think so. The intended behavior for submit buttons it to prevent them from submitting unless you call prevent_default inside the submit event handler. The navigation component you linked to in the router is entirely optional if you want a forward/backward button within your page. It isn't related to the browser's navigation buttons.
The intended behavior for submit buttons it to prevent them from submitting unless you call
prevent_defaultinside the submit event handler.
Wait, so the behavior where a button with type submit does not submit the form it's in is intended? This is clearly not what HTML does in regular browsers and if diverging from the standard is a deliberate choice, then it should definitely be documented. Also, how is calling prevent_default inside the submit handler supposed to help when the submit handler does not get called in the first place?
Additionally, as I originally stated in the issue, this behavior only emerges when there is at least one element with an onclick handler on the page. Attaching a noop onclick handler to an element somewhere completely unrelated shouldn't change the behavior of the program, right?
Everything else aside, what would be the intended way to submit forms? Using a button element is generally considered preferable over <input type=submit>, so making that the only way to submit forms would be sub-optimal (ignoring the fact that it breaks how html usually works). Isn't the root cause of the whole issue just the fact that submitting forms causes a reload/redirect which breaks the desktop renderer?
the submit handler does not get called in the first place?
That is a bug. The intended behavior on desktop when submitting forms is the onsubmit event handler gets called, but it doesn't trigger a browser navigation.
It looks like that works correctly when you don't have an onclick handler today, but when you add an onclick handler it breaks:
use dioxus::prelude::*;
use std::collections::HashMap;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut values = use_signal(HashMap::new);
let mut submitted_values = use_signal(HashMap::new);
rsx! {
div { style: "display: flex",
div { style: "width: 50%",
h1 { "Form" }
if !submitted_values.read().is_empty() {
h2 { "Submitted! ✅" }
}
// The form element is used to create an HTML form for user input
// You can attach regular attributes to it
form {
id: "cool-form",
style: "display: flex; flex-direction: column;",
// You can attach a handler to the entire form
oninput: move |ev| {
println!("Input event: {:#?}", ev);
values.set(ev.values());
ev.prevent_default();
},
// On desktop/liveview, the form will not navigate the page - the expectation is that you handle
// The form event.
// However, if your form doesn't have a submit handler, it might navigate the page depending on the webview.
// We suggest always attaching a submit handler to the form.
onsubmit: move |ev| {
println!("Submit event: {:#?}", ev);
submitted_values.set(ev.values());
},
// Regular text inputs with handlers
label { r#for: "username", "Username" }
input {
r#type: "text",
name: "username",
oninput: move |ev| {
println!("setting username");
values.set(ev.values());
}
}
// And then the various inputs that might exist
// Note for a value to be returned in .values(), it must be named!
label { r#for: "full-name", "Full Name" }
input { r#type: "text", name: "full-name" }
input { r#type: "text", name: "full-name" }
label { r#for: "email", "Email (matching <name>@example.com)" }
input { r#type: "email", pattern: ".+@example\\.com", size: "30", required: "true", id: "email", name: "email" }
label { r#for: "password", "Password" }
input { r#type: "password", name: "password" }
label { r#for: "color", "Color" }
input { r#type: "radio", checked: true, name: "color", value: "red" }
input { r#type: "radio", name: "color", value: "blue" }
input { r#type: "radio", name: "color", value: "green" }
// Select multiple comes in as a comma separated list of selected values
// You should split them on the comma to get the values manually
label { r#for: "country", "Country" }
select {
name: "country",
multiple: true,
oninput: move |ev| {
println!("Input event: {:#?}", ev);
println!("Values: {:#?}", ev.value().split(',').collect::<Vec<_>>());
},
option { value: "usa", "USA" }
option { value: "canada", "Canada" }
option { value: "mexico", "Mexico" }
}
// Safari can be quirky with color inputs on mac.
// We recommend always providing a text input for color as a fallback.
label { r#for: "color", "Color" }
input { r#type: "color", value: "#000002", name: "head", id: "head" }
// Dates!
input {
min: "2018-01-01",
value: "2018-07-22",
r#type: "date",
name: "trip-start",
max: "2025-12-31",
id: "start"
}
// CHekcboxes
label { r#for: "cbox", "Color" }
div {
label { r#for: "cbox-red", "red" }
input { r#type: "checkbox", checked: true, name: "cbox", value: "red", id: "cbox-red" }
}
div {
label { r#for: "cbox-blue", "blue" }
input { r#type: "checkbox", name: "cbox", value: "blue", id: "cbox-blue" }
}
div {
label { r#for: "cbox-green", "green" }
input { r#type: "checkbox", name: "cbox", value: "green", id: "cbox-green" }
}
div {
label { r#for: "cbox-yellow", "yellow" }
input { r#type: "checkbox", name: "cbox", value: "yellow", id: "cbox-yellow" }
}
// Buttons will submit your form by default.
button { r#type: "submit", value: "Submit", "Submit the form" }
}
}
div { style: "width: 50%",
h1 { "Oninput Values" }
pre { "{values:#?}" }
}
}
button {
onclick: move |_| { // This breaks the onsubmit button
println!("Values: {:#?}", values.read());
},
"Log values"
}
}
}
That is a bug. The intended behavior on desktop when submitting forms is the
onsubmitevent handler gets called, but it doesn't trigger a browser navigation.
Right, now we're on the same page.
It looks like that works correctly when you don't have an onclick handler today, but when you add an onclick handler it breaks
because
if there are no elements on the page with an onclick handler attached, the toplevel main div that dioxus implicitly inserts does not get an onclick event handler attached to it, which causes event.type to be click so handleClickNavigate does not get called in the first place:
@ealmloff Any news on this?