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

Splat Route doesn't handle root index path. #5841

Closed
1 task done
rossipedia opened this issue Mar 18, 2023 · 13 comments · Fixed by #6130
Closed
1 task done

Splat Route doesn't handle root index path. #5841

rossipedia opened this issue Mar 18, 2023 · 13 comments · Fixed by #6130
Labels
bug Something isn't working

Comments

@rossipedia
Copy link
Contributor

What version of Remix are you using?

1.14.3

Are all your remix dependencies & dev-dependencies using the same version?

  • Yes

Steps to Reproduce

  1. npx create-remix@latest
  2. Rename app/routes/index.tsx to app/routes/$.tsx
  3. npm run dev
  4. Open http://localhost:3000/

Expected Behavior

The default index route displays.

Actual Behavior

Nothing is shown.

See the behavior here

@akamfoad
Copy link
Contributor

That is the intended behavior, Index Routes routes are a bit special, they share the same URL as their parent route and they render inside the <Outlet /> that is rendered inside their parent route.

On the other hand, Splat Routes are last resort for Remix to match if nothing else matches. Remix (React Router really) uses an algorithm that gives each route a rank, routes that rank higher will be given priority to handle a URL.

When route X renders a <Outlet /> component, Remix will rank the routes nested under route X to find a match for a given URL. Let's say X is the root route.

  1. When the URL is the /, remix will render the Root route, and for the <Outlet /> it'll check if the root route has a nested route that is marked as index if so it'll render it, otherwise it doesn't render anything on the outlet.
  2. If the URL has something after the / for example /games, Remix will try to find a nested route that matches games if there is, it'll render it, otherwise:
  3. it'll check for a nested dynamic route, if there is, it'll render that route and pass the game string as a parameter, for example $param.tsx will get param=games, otherwise:
  4. If there is no index, and no dynamic route and the URL is not same as parent route's URL, then it'll render the splat route, because splat route as I said, is the last resort, my understanding is, its lowest ranked primitive in React Router (and Remix) router matching algorithm.

See this example, I tried to demonstrate how index and splat are matched https://stackblitz.com/edit/remix-issue-5841

@rossipedia
Copy link
Contributor Author

rossipedia commented Mar 19, 2023

@akamfoad I was asked to open this issue by a Remix team member, as this behavior doesn't match React Router's handling of the * path parameter :)

image

image

@akamfoad
Copy link
Contributor

@rossipedia I see, I wasn't aware of that.

@brophdawg11 brophdawg11 self-assigned this Apr 21, 2023
@brophdawg11 brophdawg11 added bug Something isn't working and removed bug:unverified labels Apr 21, 2023
@brophdawg11 brophdawg11 linked a pull request Apr 21, 2023 that will close this issue
@brophdawg11 brophdawg11 added the awaiting release This issue has been fixed and will be released soon label Apr 24, 2023
@brophdawg11 brophdawg11 removed their assignment Apr 24, 2023
@brophdawg11
Copy link
Contributor

This is fixed by #6130 and should be available once 1.16.0 is released

@github-actions
Copy link
Contributor

🤖 Hello there,

We just published version v0.0.0-nightly-6295eb6-20230425 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

@ryanflorence
Copy link
Member

ryanflorence commented Apr 27, 2023

I think we goofed by calling this an issue. I misunderstood in discord that the bug was "splat routes don't work unless a sibling index route is also defined".

But rather, this issue is asking "why doesn't the splat route match the parent URL like an index route?" or "how do I create a root splat route".

In current react router semantics:

  • index routes match the parent route
  • splat routes match anything deeper than the parent route

Given that, I would not expect the remix splat route to match /.

In React router, / will not match the * route:

<Route path="/" element={<Root />}>
  <Route path="*" element={<Splat />} />
</Route>

You create that router tree in remix with these files:

root.tsx
routes/$.tsx

If you want the same handling for all routes, you'll need to also have a sibling index route by the splat route to match the parent route.

root.tsx
routes/_index.tsx
routes/$.tsx

We discussed internally what would happen if we change the root route to be a pathless route:

<Route element={<Root />}>
  <Route path="*" element={<Splat />} />
</Route>

But this now causes / to be a 404, which is very unexpected and also a breaking change for Remix.

There are times where I'd personally like my root.tsx to be able to handle all paths, or have a splat route handle everything that's not defined otherwise (including /), but for now you'll just need both an index and splat.

A couple deeper questions:

  • should splat routes be able to match their parent path the way index routes do?

  • how could we allow Remix to not force the root.tsx file and allow configurations like:

    <Route path="*" element={<Root />} />
    
    // or siblings
    <>
      <Route path="/" element={<Root />}>{children}</Route>
      <Route path="*" element={<RootSplat />} />
    </>

@brophdawg11 brophdawg11 removed the awaiting release This issue has been fixed and will be released soon label Apr 27, 2023
@brophdawg11
Copy link
Contributor

This change was reverted in #6222

@jlengstorf
Copy link

fwiw I've worked on several apps (including a demo I'm building right now) where everything is intended to happen through a single route

e.g. the / route is a default state, and sub-routes are filters, etc. that display a subset of the dataset using the same components (e.g. /category/burgers)

it was surprising to me that there was no way to define a "everything goes through here" file

maybe this could be a special opt-in format? routes/_$.tsx or something to indicate that it's a catch-all route inclusive of the index?

@jlengstorf
Copy link

for future googlers, this was my workaround, assuming you've got a file structure like this:

routes/
  - _index.tsx
  - $.tsx

set up routes/_index.tsx to do all the logic, and in your $.tsx just export all the index pieces, such as:

import RouteComponent from './_index';

export { loader } from './_index';

export default RouteComponent;

@brophdawg11
Copy link
Contributor

With remix.config.js routes you get to do whatever you want! You can even tag the same file to multiple paths with a unique ID. So for the "everything" use case, delete your routes/ dir, stick an everything.tsx route file next to root.tsx, then add this to your remix config:

  routes: async (defineRoutes) => {
    return defineRoutes((route) => {
      route("/", "everything.tsx", { id: "index" });
      route("*", "everything.tsx", { id: "splat" });
    });
  },

@jlengstorf
Copy link

With remix.config.js routes you get to do whatever you want!

oh dang! what version did that land in? I'll update my demo

@brophdawg11
Copy link
Contributor

routes has been there since the beginning (no file convention is going to make everyone happy 😉 ) - but the ability to add a specific ID allowing you to point multiple paths to the same file came in 1.9.0

@ryanflorence
Copy link
Member

v0.1.0, file system routes are whack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants