navigator.can_go_back() fails to hydrate
Problem
Using navigator.can_go_back() from use_navigator() make closure for event in html element like input does not work after refreshing the page.
Steps To Reproduce
Steps to reproduce the behavior:
There are 2 file of components.
- NavSimpleComponent.rs
use dioxus::prelude::*;
use dioxus_free_icons::{icons::fa_solid_icons::FaAngleLeft, Icon};
use crate::Route;
#[component]
pub fn NavSimpleComponent(name: String) -> Element {
let navigator = use_navigator();
rsx! {
header {
class:"flex justify-start items-center app-bg-color-primary px-5 py-2 space-x-4",
if (navigator.can_go_back()){
button {
class: "app-button-circle item-navbar",
GoBackButton{
Icon{
width: 18,
height: 18,
class: "app-color-primary",
icon: FaAngleLeft
},
}
},
}
else{
Link {
class: "app-button-circle item-navbar",
to: Route::Home{},
Icon{
width: 18,
height: 18,
class: "app-color-primary",
icon: FaAngleLeft
},
},
}
h1 { class:"hidden md:block app-page-title", "{name}"},
},
}
}
- WorkspaceCreatePage.rs
use std::sync::Arc;
use crate::dioxus_elements::FileEngine;
use crate::components::NavSimpleComponent;
use dioxus::html::HasFileData;
use dioxus::prelude::*;
use convert_case::{Case, Casing};
use dioxus_free_icons::icons::fa_regular_icons::{FaFileImage};
use dioxus_free_icons::Icon;
#[component]
pub fn WorkspaceCreatePage(workspace_type: String) -> Element {
let name = format!("Tambah {}", workspace_type.to_case(Case::Title));
let mut files_uploaded: Signal<Vec<(String, String)>> = use_signal(|| Vec::<(String, String)>::new());
let mut file_size: Signal<Vec<usize>> = use_signal(|| Vec::<usize>::new());
rsx! {
div { class: "min-h-screen overflow-y-auto relative",
div {
class: "w-full",
NavSimpleComponent{name: name},
div {
class:"w-full",
form {
class:" w-full md:w-2/3 mx-auto my-6",
div {
class: "rounded shadow p-4 my-3",
h3{
class:"app-color-primary mb-2 font-bold",
"Detail"
},
div {
label { class: "app-label", r#for:"name", "Nama*" }
input { class: "app-input", r#type:"text", name:"name", id:"name", required:true }
},
div {
label { class: "app-label", r#for:"telp", "No. Telp.*" }
input { class: "app-input", r#type:"telp", name:"telp", id:"telp", required:true }
},
div {
label { class: "app-label", r#for:"email", "Email*" }
input { class: "app-input", r#type:"email", name:"email", id:"email", required:true }
},
div {
label { class: "app-label", r#for:"alamat", "Alamat*" }
textarea { class: "app-input", name:"alamat", id:"alamat", rows: "4", required:true }
},
}
div {
class: "rounded shadow p-4 my-3",
h3{
class:"app-color-primary mb-2 font-bold",
"Additional"
},
div {
label { class: "app-label", r#for:"gmaplink", "Google Map Link" }
input { class: "app-input", r#type:"text", name:"gmaplink", id:"gmaplink" }
}
div {
label { class: "app-label","Foto" },
div {
class:"app-dropzone",
ondrop: move |event| async move{
if let Some(file_engine) = event.files() {
let files = file_engine.files();
for file_name in &files {
// print!("{}",file_name);
if let Some(contents) = file_engine.read_file_to_string(file_name).await {
files_uploaded.write().push((file_name.clone(), contents));
}
}
}
},
ondragover: |event|{
event.prevent_default();
},
div {
class:"text-center",
Icon{
class:"mx-auto h-12 w-12",
width: 24,
height: 24,
icon: FaFileImage
},
h3{
class:"mt-2 text-sm font-medium text-gray-900",
label{
class:"relative cursor-pointer",
r#for:"file-upload",
span{
"Drag and drop",
}
span{
class:"app-color-primary font-bold ",
" or browse",
}
span{
" to upload",
}
input{
id:"file-upload",
name:"file_upload",
r#type:"file",
class:"sr-only",
multiple: true,
onchange: move |event| async move{
if let Some(file_engine) = event.files() {
let files = file_engine.files();
for file_name in &files {
if let Some(contents) = file_engine.read_file_to_string(file_name).await {
files_uploaded.write().push((file_name.clone(), contents.clone()));
file_size.write().push(contents.clone().len());
}
}
}
},
}
}
}
p{
class:"mt-1 text-xs text-gray-500",
"PNG, JPG, GIF up to 10MB"
}
}
}
},
ul {
for (name, content) in files_uploaded().iter() {
li { "{name}: {content.len()}" }
}
}
}
div {
class:"flex justify-center items-center",
input { class: "app-button", r#type:"submit" }
}
}
},
}
}
}
}
Expected behavior
the event in HTML is still working fine even after refreshing the page.
Screenshots
Environment:
- Dioxus version: 0.6.0 with features = ["router", "fullstack"]
- Rust version: rustc 1.83.0 (90b35a623 2024-11-26)
- OS info: Windows 11
- App platform: web
Questionnaire
I can reproduce this issue with dioxus fullstack in 0.6 with this code:
// [dependencies]
// dioxus = { version = "0.6.3", features = ["fullstack", "router"] }
use dioxus::prelude::*;
fn main() {
launch(App);
}
fn App() -> Element {
rsx! {
Router::<Route> {}
}
}
#[derive(Routable, Clone, PartialEq, Debug)]
pub enum Route {
#[route("/")]
Home,
#[route("/about")]
About,
}
#[component]
pub fn Home() -> Element {
let navigator = use_navigator();
let mut count = use_signal(|| 0);
rsx! {
header {
class:"flex justify-start items-center app-bg-color-primary px-5 py-2 space-x-4",
if navigator.can_go_back() {
button {
class: "app-button-circle item-navbar",
onclick: move |_| {
count += 1;
},
"{count}"
},
}
else {
div {
Link {
class: "app-button-circle item-navbar",
to: Route::About,
"Go to about"
},
button {
class: "app-button-circle item-navbar",
onclick: move |_| {
count += 1;
},
"{count}"
},
}
}
},
}
}
#[component]
pub fn About() -> Element {
rsx! {
Link {
class: "app-button-circle item-navbar",
to: Route::Home,
"Go to Home"
},
"About this page"
}
}
I think this is a hydration error where the server and client don't agree on navigator.can_go_back(). The value should always be the same on the initial render and only change to the real client side value after hydration has finished. We need to emulate this behavior in the dioxus router:
#[component]
pub fn Home() -> Element {
let navigator = use_navigator();
let mut count = use_signal(|| 0);
let mut navigator_can_go_back = use_signal(|| false);
use_effect(move || {
navigator_can_go_back.set(navigator.can_go_back());
});
rsx! {
header {
class:"flex justify-start items-center app-bg-color-primary px-5 py-2 space-x-4",
if navigator_can_go_back() {
button {
class: "app-button-circle item-navbar",
onclick: move |_| {
count += 1;
},
"{count}"
},
}
else {
div {
Link {
class: "app-button-circle item-navbar",
to: Route::About,
"Go to about"
},
button {
class: "app-button-circle item-navbar",
onclick: move |_| {
count += 1;
},
"{count}"
},
}
}
},
}
}