AlexxNB / tinro

Highly declarative, tiny, dependency free router for Svelte's web applications.
MIT License
669 stars 30 forks source link

Issue with route matching #39

Closed silentworks closed 3 years ago

silentworks commented 3 years ago

I currently have a project where I have the following routes

The issue I am currently having is that when a user visits /map its also matching with /:username so I get both routes loading when I think it should have stopped from the first route was matched. I know this has been discussed before and you suggested to change the root level routing to something like /u/:username, but in a lot of standard web applications you don't need to do this, if you look at Github for example, you can load other routes at the root level while still having access to /:username. I noticed in react router they solve this with the exact property on their Route component. I also think react router have a central place (Router component) where route matching happens while it looks like this happens on a per Route component basis with tinro.

AlexxNB commented 3 years ago

We already discussed same problem here: https://github.com/AlexxNB/tinro/issues/31

I think to add except option. Something like this.

<Route path="/map">...</Route>
<Route path="/:username" except="/map">..</Route>

But now it doesn't look flexible.

silentworks commented 3 years ago

I don't think except would be a good option anyway because then you have to state what not to look for. Is this behaviour due to the route inheritance feature? In server-side routers that I've worked within the past, it seems route order is normally how precedence takes place, so if I define /map path before /:username then when /map is matched it will only serve up the route for that and not try to load the route for /:username.

silentworks commented 3 years ago

It turns out that React Router is achieving this by using a special component called Switch, I'm going to dive into it and see if there is anything that could be useful for tinro. Example below.

<Switch>
  <Route exact path='/'>
    <Home />
  </Route>
  <Route path='/map'>
    <Topics />
  </Route>
  <Route path='/:username'>
    Hello Person
  </Route>
</Switch>
AlexxNB commented 3 years ago

The rule "all matched paths are show" should work, because routes may be in different components and their order may not be clear for programmer. I don't want use exact keyword, because it describes exact and non-exact routes (/ and /*).

It may be only attribute like:

<Route path="/map" only>...</Route>
<Route path="/:username">..</Route>

Internally it is harder to implement than except option.

Simpler to realize if parent of nested routes will have the option like switch:

<Route switch>
  <Route path="/map">...</Route>
  <Route path="/:username">..</Route>
</Route>
silentworks commented 3 years ago

Yeah the parent having a switch would be nice, but after looking through the tinro code I can see it might be a bit hard to implement. But you would know the code better than me so maybe it's not as hard as I'm imagining.

silentworks commented 3 years ago

Here is a link to the codesandbox example using the react router that I tested out earlier. Switch only works for the immediate routes and not their children, so if you want the same behaviour for the children you would have to add another Switch component like I did in this example.

Line 136 and 159 is where the switches are.

https://codesandbox.io/s/nested-routes-7-forked-5m8bw?file=/src/App.js

silentworks commented 3 years ago

This is also a good breakdown of what happens when using Switch in react-router.

https://stackoverflow.com/a/47404944/811799

AlexxNB commented 3 years ago

I added firstmatch option for non-exact route to show only first matched nested route. https://github.com/AlexxNB/tinro#show-first-matched-nested-route-only

AlexxNB commented 3 years ago

I found maybe a better approach:

<script>
    import {router,Route} from 'tinro';
    let meta = router.meta()
</script>
<Route path="/:username">
   {#if $meta.params.username == 'map'}
      This is MAP page
   {:else}
      This is user {$meta.params.username}
   {/if}
</Route>
silentworks commented 3 years ago

This would not be a better approach since I would have to create this logic inside my routes instead of the router handling it as part of its process.