TanStack / router

🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
https://tanstack.com/router
MIT License
7.27k stars 495 forks source link

`useMatch` not able match more than one param #142

Closed chungweileong94 closed 2 years ago

chungweileong94 commented 2 years ago

Describe the bug If my URL contains more than one param like /menu/:menuId/sub/:subId, useMatch will only return menuId when the route is match. I also use useMatchRoute to make sure that the route is really matches.

To Reproduce Steps to reproduce the behavior:

  1. Create a route that contains two params or more, like /menu/:menuId/sub/:subId
  2. In the corresponding route element, call useMatch
  3. And observe the params that return by useMatch

Expected behavior The useMatch should return both menuId & subId

Screenshots

Desktop (please complete the following information):

joshuabrokaw commented 2 years ago

If you look at the API reference, there's another hook useMatches link

this should give you what you are looking for.

happy hacking!

chungweileong94 commented 2 years ago

If you look at the API reference, there's another hook useMatches link

this should give you what you are looking for.

happy hacking!

I did try the useMatches, however, it only returns one match for some reason🤔

chungweileong94 commented 2 years ago

So I didn't realize that the order of the routes does matter, which makes sense. For instance,

// This won't work
{path: '/post/:postId', element: <Page />},
{path: '/post/:postId/comment/:commentId', element: <Page />},

// This works
{path: '/post/:postId/comment/:commentId', element: <Page />},
{path: '/post/:postId', element: <Page />},
chungweileong94 commented 2 years ago

I personally find that the useMatches is quite difficult to use in scenarios like if I want to get all the match params. It would be great if we have something like const params = useParams().

As for now, I created a custom hook to merge all the match params from useMatches, it feels hacky, but works for me

export const useParams = <TGenerics extends PartialGenerics = DefaultGenerics>() => {
  const matches = useMatches<TGenerics>();
  const params = useMemo(
    () =>
      matches.reduce((result, match) => {
        return {...result, ...match.params};
      }, {} as TGenerics['Params']),
    [matches],
  );
  return params;
};
joshuabrokaw commented 2 years ago

do you have your MakeGenerics setup?

pulled from the Kitchen Sink example.

export type LocationGenerics = MakeGenerics<{
  Params: {
    postId: string;
    commentId: string;
  };
}>;

const location = new ReactLocation<LocationGenerics>();

const routes: Route<LocationGenerics>[] = [ 
  ... 
  {path: '/post/:postId/comment/:commentId', element: <Page />},
  {path: '/post/:postId', element: <Page />}, 
  ... 
];

With that setup are you still not able to do this?

const { params: { postId, commentId }} = useMatch<LocationGenerics>();

chungweileong94 commented 2 years ago

do you have your MakeGenerics setup?

pulled from the Kitchen Sink example.

export type LocationGenerics = MakeGenerics<{
  Params: {
    postId: string;
    commentId: string;
  };
}>;

const location = new ReactLocation<LocationGenerics>();

const routes: Route<LocationGenerics>[] = [ 
  ... 
  {path: '/post/:postId/comment/:commentId', element: <Page />},
  {path: '/post/:postId', element: <Page />}, 
  ... 
];

With that setup are you still not able to do this?

const { params: { postId, commentId }} = useMatch<LocationGenerics>();

Not sure if I understand you correctly, but MakeGenerics or TypeScript in general, has nothing to do with the problem that I was facing. in fact, it was caused by the order of how I define the routes.

joshuabrokaw commented 2 years ago

I personally find that the useMatches is quite difficult to use in scenarios like if I want to get all the match params. It would be great if we have something like const params = useParams().

As for now, I created a custom hook to merge all the match params from useMatches, it feels hacky, but works for me

export const useParams = <TGenerics extends PartialGenerics = DefaultGenerics>() => {
  const matches = useMatches<TGenerics>();
  const params = useMemo(
    () =>
      matches.reduce((result, match) => {
        return {...result, ...match.params};
      }, {} as TGenerics['Params']),
    [matches],
  );
  return params;
};

I was referencing what you said here. If you have your MakeGenerics setup, you should be able to pull off all the params, without having to write a custom hook.

chungweileong94 commented 2 years ago

@joshuabrokaw I see. useMatches does work, but it returns an array like

[
  {
    // ...
    params: {postId: 1},
  },
  {
    // ...
    params: {postId: 1, commentId: 2},
  },
];

Which means I have to search through the array and extract the params I need (in this case, I need both postId & commentId).

So I create a custom hook just to to extract and merge all the params

joshuabrokaw commented 2 years ago

Yeah, my point is that you don't have to do that, if you setup your MakeGenerics like in the example I showed you.

Do you have MakeGenerics setup? You won't need to merge them all together like that if you do.

Assuming your routes are ordered correctly.

chungweileong94 commented 2 years ago

Yeah, my point is that you don't have to do that, if you setup your MakeGenerics like in the example I showed you.

Do you have MakeGenerics setup? You won't need to merge them all together like that if you do.

Hmm, I understand that, but from what I know MakeGenerics is just a type. The problem I was facing will happen on both JavaScript & TypeScript, setup the MakeGenerics won't magically merge them all.

For Example:

export type LocationGenerics = MakeGenerics<{
  Params: {
    postId: string;
    commentId: string;
  };
}>;

const location = new ReactLocation<LocationGenerics>();

const routes: Route<LocationGenerics>[] = [ 
  ... 
  {path: '/post/:postId/comment/:commentId', element: <Page />},
  {path: '/post/:postId', element: <Page />}, 
  ... 
];

...

// Let say the route is "http:locahost:3000/post/1/comment/2"
const matches = useMatches<LocationGenerics>();

The matches will be an array of all match results, that's why I still have to search through the array to get the correct match that contains the params I want

[
  {
    // ...
    params: {postId: 1},
  },
  {
    // ...
    params: {postId: 1, commentId: 2},
  },
];

If I use useMatch, it will return

{
  // ...
  params: {postId: 1},
},

Which is definitely not something I want to use.

tannerlinsley commented 2 years ago

Why won’t useMatch().params not work? They are merged down the match tree to include parent params.

Tanner Linsley On Dec 23, 2021, 9:50 AM -0700, Chung Wei Leong @.***>, wrote:

Yeah, my point is that you don't have to do that, if you setup your MakeGenerics like in the example I showed you. Do you have MakeGenerics setup? You won't need to merge them all together like that if you do. Hmm, I understand that, but from what I know MakeGenerics is just a type. The problem I was facing will happen on both JavaScript & TypeScript, setup the MakeGenerics won't magically merge them all. For Example: export type LocationGenerics = MakeGenerics<{ Params: { postId: string; commentId: string; }; }>;

const location = new ReactLocation();

const routes: Route[] = [ ... {path: '/post/:postId/comment/:commentId', element: }, {path: '/post/:postId', element: }, ... ];

...

// Let say the route is "http:locahost:3000/post/1/comment/2" const matches = useMatches(); The matches will be an array of all match results, that's why I still have to search through the array to get the correct match that contains the params I want [ { // ... params: {postId: 1}, }, { // ... params: {postId: 1, commentId: 2}, }, ]; If I use useMatch, it will return { // ... params: {postId: 1}, }, Which is definitely not something I want to use. — Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you are subscribed to this thread.Message ID: @.***>

chungweileong94 commented 2 years ago

Why won’t useMatch().params not work? They are merged down the match tree to include parent params. Tanner Linsley On Dec 23, 2021, 9:50 AM -0700, Chung Wei Leong @.>, wrote: > Yeah, my point is that you don't have to do that, if you setup your MakeGenerics like in the example I showed you. > Do you have MakeGenerics setup? You won't need to merge them all together like that if you do. Hmm, I understand that, but from what I know MakeGenerics is just a type. The problem I was facing will happen on both JavaScript & TypeScript, setup the MakeGenerics won't magically merge them all. For Example: export type LocationGenerics = MakeGenerics<{ Params: { postId: string; commentId: string; }; }>; const location = new ReactLocation(); const routes: Route[] = [ ... {path: '/post/:postId/comment/:commentId', element: }, {path: '/post/:postId', element: }, ... ]; ... // Let say the route is "http:locahost:3000/post/1/comment/2" const matches = useMatches(); The matches will be an array of all match results, that's why I still have to search through the array to get the correct match that contains the params I want [ { // ... params: {postId: 1}, }, { // ... params: {postId: 1, commentId: 2}, }, ]; If I use useMatch, it will return { // ... params: {postId: 1}, }, Which is definitely not something I want to use. — Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you are subscribed to this thread.Message ID: @.>

Uh my bad, you are right, I'm actually confused by my current code setup. Yeah, it works as expected now👍