Skip to content
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

Users first navigation on <a> tag does not update URL if inside <ParentRoute> without <Outlet /> #3605

Open
luxalpa opened this issue Feb 14, 2025 · 2 comments

Comments

@luxalpa
Copy link
Contributor

luxalpa commented Feb 14, 2025

Describe the bug
Using the example code below, the first navigation of the user is ignored. All subsequent navigations work fine however. Also, the navigate function appears to be working fine too. It works if I declare an <Outlet /> component, however, see "Additional Context" for why this is not always desirable.

Leptos Dependencies

[patch.crates-io]
leptos = { git = "https://github.com/leptos-rs/leptos.git", rev = "590728e47e99c0f932cb39abf6c30807c30e5126" }
leptos_meta = { git = "https://github.com/leptos-rs/leptos.git", rev = "590728e47e99c0f932cb39abf6c30807c30e5126" }
leptos_router = { git = "https://github.com/leptos-rs/leptos.git", rev = "590728e47e99c0f932cb39abf6c30807c30e5126" }
leptos_actix = { git = "https://github.com/leptos-rs/leptos.git", rev = "590728e47e99c0f932cb39abf6c30807c30e5126" }
reactive_graph = { git = "https://github.com/leptos-rs/leptos.git", rev = "590728e47e99c0f932cb39abf6c30807c30e5126" }

To Reproduce

<Router>
    <main>
        <ul>
            <li><a href="/">"Main"</a></li>
            <li><a href="/collection">"Collection"</a></li>
            <li><a href="/other">"Other"</a></li>
        </ul>
        <Routes fallback=move || "Not found.">
            <ParentRoute path=StaticSegment("") view=|| ()>
                <Route path=StaticSegment("") view=|| () />
                <Route path=StaticSegment("collection") view=|| () />
                <Route path=StaticSegment("other") view=|| () />
            </ParentRoute>
            <Route path=WildcardSegment("any") view=NotFound/>
        </Routes>
    </main>
</Router>

Expected behavior
When the user clicks on the link, they should navigate to the page.

Additional context
My Application is largely ignoring leptos-router: I only use the router for the actual routing behavior, but I render all of the components manually based on a use_location trigger. I do this because some bits of my application are using a routing scheme that does not fit well into the traditional routing behavior (or nested routing behavior) of a web page. As it is more an App than a pure web page, it has some application states mapped to specific routes. For example, my home route ("/") and my "/search" route are the same page; their only difference is that the user has entered something in the search box presented on the front page, and accordingly, search results are being displayed.
Because use_location didn't work outside of a route in 0.6 (probably still doesn't in 0.7 although I haven't tested yet), I have my entire application inside the view on a <ParentRoute>. The route elements inside of that ParentRoute are all view=|| () as all the actual displaying of the content is done by manually parsing the URL in a Memo via use_location() and then using that information in various components to alter their content.

@mahdi739
Copy link
Contributor

Hi, I took a look at nested_router.rs, and based on my current (albeit naive) understanding, here's what I think is happening:

In the implementation of Render for NestedRoutesView, it first attempts to build the routes recursively:

route.build_nested_route(
&url,
base,
&mut loaders,
&mut outlets,
&outer_owner,
);

Then, it creates the first outlet on its own, assuming that there should eventually be an outlet somewhere:

let outlet = RouteContext {
id: self.as_id(),
url,
trigger: trigger.clone(),
params,
owner: owner.clone(),
matched,
view_fn: Arc::new(Mutex::new(Box::new(|| {
Suspend::new(Box::pin(async { ().into_any() }))
}))),
base: base.clone(),
};
outlets.push(outlet.clone());

During the rebuild phase, it matches the current view's ID with the first view's ID in the stack, which is again the current view because a real outlet was not actually included in the routes:

if id != current.id {

As a result, the condition is never met, and the full_loaders that were supposed to be added here are never included:

let (full_tx, full_rx) = oneshot::channel();
let full_tx = Mutex::new(Some(full_tx));
full_loaders.push(full_rx);

Consequently, later in the implementation, when it waits for the loaders to finish, they never complete, and the notifying never happens:

ScopedFuture::new(async move {
let triggers = join_all(loaders).await;
for trigger in triggers {
trigger.notify();
}
matched_view.rebuild(&mut *view.borrow_mut());
})

If you don't push the outlet at line 617, your problem will be solved. However, I don't think this is the right approach, as it might cause other issues.
Similarly, if you generate a new ID at line 606, your problem will also be solved, but again, I don't believe this is the correct solution.

My suggestion is to add an argument to the build_nested_route function called initial_outlet_id, which represents the first outlet's ID. When this function is called from the Render implementation, it would be given a new ID. However, during the recursive building process, it would use self.as_id() instead.
I will create a PR to show my suggestion, but since I have no idea how routing works, I don't think it's the right solution.

mahdi739 added a commit to mahdi739/leptos that referenced this issue Mar 10, 2025
@gbj
Copy link
Collaborator

gbj commented Mar 10, 2025

It works if I declare an <Outlet /> component, however, see "Additional Context" for why this is not always desirable.

I read the additional context but I'm not sure I understand why using an <Outlet/> would be undesirable, as all the views are empty anyway.

If anyone sees a straightforward fix to this, I'm happy to look at a PR, but to me this otherwise is WAD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants