@@ -717,103 +717,149 @@ const Routes = ({ children, location: locationProp }: RouterRoutesProps) => {
717717 [ ]
718718 ) ;
719719
720- // Find matching route and calculate the new matched path
721- const matchResult = useMemo ( ( ) => {
722- // Helper to calculate matched path
723- const calcMatchedPath = ( matchedPortion : string ) : string => {
724- if ( ! parentMatchedPath || parentMatchedPath === '/' ) {
725- return matchedPortion ;
726- } else if ( matchedPortion === '/' ) {
727- return parentMatchedPath ;
728- } else {
729- return `${ parentMatchedPath } ${ matchedPortion } ` ;
730- }
731- } ;
732-
733- let bestMatch : {
734- route : RouteConfig ;
735- matchedPath : string ;
736- params : Record < string , string | undefined > ;
737- } | null = null ;
738-
739- for ( const route of routes ) {
740- if ( route . index && ( pathname === '/' || pathname === '' ) ) {
741- // Index route: matched path stays the same
742- const newMatchedPath = parentMatchedPath || '/' ;
743- return { route, matchedPath : newMatchedPath , params : { } } ;
744- }
720+ const findMostSpecificMatchingRoute = useCallback (
721+ (
722+ routes : RouteConfig [ ] ,
723+ parentMatchedPath : string ,
724+ pathname : string ,
725+ fullPathname : string
726+ ) => {
727+ // Helper to calculate matched path
728+ const calcMatchedPath = ( matchedPortion : string ) : string => {
729+ if ( ! parentMatchedPath || parentMatchedPath === '/' ) {
730+ return matchedPortion ;
731+ } else if ( matchedPortion === '/' ) {
732+ return parentMatchedPath ;
733+ } else {
734+ return `${ parentMatchedPath } ${ matchedPortion } ` ;
735+ }
736+ } ;
745737
746- // Pathless layout route: path is undefined (not empty string) and has children
747- // Match if any child route would match the pathname
748- if ( route . path === undefined && route . children ) {
749- if ( childRouteMatches ( route . children , pathname ) ) {
738+ // The best match may be a route from a pathless layout route child.
739+ // routeForBestMatch is the route to reach the best match at this level of recursion.
740+ let bestMatch : {
741+ route : RouteConfig ;
742+ matchedPath : string ;
743+ params : Record < string , string | undefined > ;
744+ } | null = null ;
745+ let routeForBestMatch : {
746+ route : RouteConfig ;
747+ matchedPath : string ;
748+ params : Record < string , string | undefined > ;
749+ } | null = null ;
750+
751+ for ( const route of routes ) {
752+ if ( route . index && ( pathname === '/' || pathname === '' ) ) {
753+ // Index route: matched path stays the same
750754 const newMatchedPath = parentMatchedPath || '/' ;
751755 return { route, matchedPath : newMatchedPath , params : { } } ;
752756 }
753- }
754757
755- if ( route . path !== undefined ) {
756- const match = matchPath ( route . path , pathname ) ;
757- if ( match ) {
758- const matchedPortion = match . pathnameBase || '/' ;
759- const newMatchedPath = calcMatchedPath ( matchedPortion ) ;
760-
761- const currentMatch = {
762- route,
763- matchedPath : newMatchedPath ,
764- params : match . params ,
765- } ;
766-
767- // If no best match yet, use this one
768- if ( ! bestMatch ) {
769- bestMatch = currentMatch ;
758+ // Pathless layout route: path is undefined (not empty string) and has children
759+ // Match if any child route would match the pathname
760+ if ( route . path === undefined && route . children ) {
761+ const childMatch = findMostSpecificMatchingRoute (
762+ route . children ,
763+ parentMatchedPath ,
764+ pathname ,
765+ fullPathname
766+ ) ;
767+
768+ if (
769+ childMatch &&
770+ // If no best match yet, or the child route is more specific than the current best, use this one
771+ ( ! bestMatch ||
772+ ( bestMatch . route . path &&
773+ childMatch . route . path &&
774+ isMoreSpecific (
775+ bestMatch . route . path ,
776+ childMatch . route . path
777+ ) ) )
778+ ) {
779+ const newMatchedPath = parentMatchedPath || '/' ;
780+ bestMatch = childMatch ;
781+ routeForBestMatch = {
782+ route,
783+ matchedPath : newMatchedPath ,
784+ params : { } ,
785+ } ;
786+
770787 // If this match doesn't use a catch-all, return immediately
771- if ( ! hasCatchAll ( route . path ) ) {
772- return bestMatch ;
788+ if ( ! hasCatchAll ( bestMatch . route . path ! ) ) {
789+ return routeForBestMatch ;
773790 }
774791 // Otherwise, keep looking for more specific matches
775- continue ;
776792 }
793+ }
777794
778- // Check if this route is more specific than the current best
779- if (
780- bestMatch . route . path &&
781- isMoreSpecific ( bestMatch . route . path , route . path )
782- ) {
783- bestMatch = currentMatch ;
784- // If this match doesn't use a catch-all, return immediately
785- if ( ! hasCatchAll ( route . path ) ) {
786- return bestMatch ;
795+ if ( route . path !== undefined ) {
796+ const match = matchPath ( route . path , pathname ) ;
797+ if ( match ) {
798+ const matchedPortion = match . pathnameBase || '/' ;
799+ const newMatchedPath = calcMatchedPath ( matchedPortion ) ;
800+
801+ const currentMatch = {
802+ route,
803+ matchedPath : newMatchedPath ,
804+ params : match . params ,
805+ } ;
806+
807+ // If no best match yet, or this route is more specific than the current best, use this one
808+ if (
809+ ! bestMatch ||
810+ ( bestMatch . route . path &&
811+ isMoreSpecific (
812+ bestMatch . route . path ,
813+ route . path
814+ ) )
815+ ) {
816+ bestMatch = routeForBestMatch = currentMatch ;
817+ // If this match doesn't use a catch-all, return immediately
818+ if ( ! hasCatchAll ( route . path ) ) {
819+ return routeForBestMatch ;
820+ }
821+ // Otherwise, keep looking for more specific matches
787822 }
788823 }
789824 }
790825 }
791- }
792826
793- // If we found a match (possibly a catch-all), return it
794- if ( bestMatch ) {
795- return bestMatch ;
796- }
827+ // If we found a match (possibly a catch-all), return it
828+ if ( routeForBestMatch ) {
829+ return routeForBestMatch ;
830+ }
797831
798- // Check for catch-all route (path="*")
799- const catchAll = routes . find ( r => r . path === '*' ) ;
800- if ( catchAll ) {
801- const match = matchPath ( '*' , pathname ) ;
802- return {
803- route : catchAll ,
804- matchedPath : fullPathname ,
805- params : match ?. params ?? { } ,
806- } ;
807- }
808- return null ;
809- } , [
810- routes ,
811- parentMatchedPath ,
812- pathname ,
813- childRouteMatches ,
814- isMoreSpecific ,
815- fullPathname ,
816- ] ) ;
832+ // Check for catch-all route (path="*")
833+ const catchAll = routes . find ( r => r . path === '*' ) ;
834+ if ( catchAll ) {
835+ const match = matchPath ( '*' , pathname ) ;
836+ return {
837+ route : catchAll ,
838+ matchedPath : fullPathname ,
839+ params : match ?. params ?? { } ,
840+ } ;
841+ }
842+ return null ;
843+ } ,
844+ [ isMoreSpecific ]
845+ ) ;
846+ // Find a matching route and calculate the new matched path
847+ const matchResult = useMemo (
848+ ( ) =>
849+ findMostSpecificMatchingRoute (
850+ routes ,
851+ parentMatchedPath ,
852+ pathname ,
853+ fullPathname
854+ ) ,
855+ [
856+ findMostSpecificMatchingRoute ,
857+ routes ,
858+ parentMatchedPath ,
859+ pathname ,
860+ fullPathname ,
861+ ]
862+ ) ;
817863
818864 // Now that all hooks have been called, we can safely return early
819865 // if we're outside the basename scope
0 commit comments