fenok / react-router-typesafe-routes

Comprehensive and extensible type-safe routes for React Router v6 with first-class support for nested routes and param validation.
MIT License
151 stars 3 forks source link

v6 uninline child #9

Closed thejuan closed 2 years ago

thejuan commented 2 years ago

Playing with the v6 branch. It'd be nice if you could something like

const detailsRoutes = PRODUCT.DETAILS.uninline(PRODUCT).

So you could then use inline children to get the easy dot notation access, but break it apart when creating routes.

thejuan commented 2 years ago

Or even

const details = useRelativeRoutes(PRODUCT.DETAILS) and it uses current location information to break it apart.

fenok commented 2 years ago

Hello and thanks for your interest in the library!

I thought about providing a way to extract/uninline a child route, but eventually, I settled on the idea that it should be done during routes definition:

const DETAILS = route("details");

const PRODUCT = route("product/:id", {}, { DETAILS });

const ROUTE_PARTS = {
  DETAILS
}

const ROUTES = {
  PRODUCT
}

This way, we explicitly allow DETAILS to be used independently, and we can also use PRODUCT.DETAILS.

What I considered:

Regarding your suggestions:

Please let me know if I misunderstood something.

thejuan commented 2 years ago

Our use case is mainly to allow relative Navigate to redirect default paths without having to know parameters to use .buildPath inside a Routes I have currently written a helper to do it relativeFrom Maybe there is a better way to achieve this?

  <Route path={roleLocations.Definition.Instance.path} element={<RoleDefinitionWizard />}>
                  <Route path={Basics.path} element={<RoleBasicsView />}>
                    <Route path={Basics.Title.path} element={<RoleBasicsTitleView />} />
                   ...
                    <Route path="" element={<Navigate replace to={relativePathFrom(Basics, Basics.Title)} />} />
                  </Route>

Defining everything as un-inlined looses alot of meaning. i.e. What is the route Title? Maybe it belongs to many parents, then we end up naming them BasicsTitle

This helper solves my problem, so maybe its not needed as part of the API

fenok commented 2 years ago

Oh, now I understand what you want. Unfortunately, at the moment the only way to achieve that is to extract the child route beforehand and call .buildRelativeUrl() (or .buildRelativePath()) on it.

It would look like this:

<Route path={roleLocations.Definition.Instance.path} element={<RoleDefinitionWizard />}>
  <Route path={roleLocations.Definition.Instance.Basics.path} element={<RoleBasicsView />}>
    <Route path={roleLocations.Definition.Instance.Basics.Title.path} element={<RoleBasicsTitleView />} />
    <Route index element={<Navigate replace to={Title.buildRelativeUrl({})} />} />
  </Route>
</Route>

Note that, in your snippet, Basics has to be equal to roleLocations.Definition.Instance.Basics (because you use absolute .path field), so I just wrote it explicitly. I also used index instead of path="", because, AFAIK, it's the intended way to define such routes.

I would advise against using relativeFrom, because it's not type-safe (what if Title had a path parameter?).

Defining everything as un-inlined looses alot of meaning

I totally agree! I thought that uninlining child routes is a relatively rare use-case, but now I see that I might be wrong. I can't think of a convenient and comprehensive API for this, though.

Sorry for the late response 😅

fenok commented 2 years ago

I mean, PRODUCT.DETAILS.SUB_DETAILS.WHATEVER.uninline(PRODUCT.DETAILS) === SUB_DETAILS.WHATEVER is comprehensive and probably doable, but is it really convenient?

fenok commented 2 years ago

Ok, I think I got this. I published a new version under next tag which includes API for children uninlining. It can be used like this:

<Route path={roleLocations.Definition.Instance.path} element={<RoleDefinitionWizard />}>
  <Route path={roleLocations.Definition.Instance.Basics.path} element={<RoleBasicsView />}>
    <Route path={roleLocations.Definition.Instance.Basics.Title.path} element={<RoleBasicsTitleView />} />
    <Route index element={<Navigate replace to={roleLocations.Definition.Instance.Basics.$.Title.buildRelativeUrl({})} />} />
  </Route>
</Route>

The mental model here is that $ cuts everything at the left side, and we get just Title as if it was uninlined.

Internally, $ just contains original child routes, so it was very easy to implement 😅

thejuan commented 2 years ago

Amazing! Thankyou