@@ -63,7 +63,10 @@ export const useParams = () => useContext(ParamsCtx);
6363 * Part 1, Hooks API: useRoute and useLocation
6464 */
6565
66- // Internal version of useLocation to avoid redundant useRouter calls
66+ // Internal location hooks avoid redundant context reads and navigation callbacks.
67+
68+ const usePathnameFromRouter = ( router ) =>
69+ relativePath ( router . base , router . hook ( router ) [ 0 ] ) ;
6770
6871const useLocationFromRouter = ( router ) => {
6972 const [ location , navigate ] = router . hook ( router ) ;
@@ -89,53 +92,31 @@ export const useSearch = () => {
8992export const matchRoute = ( parser , route , path , loose ) => {
9093 // if the input is a regexp, skip parsing
9194 const { pattern, keys } =
92- route instanceof RegExp
93- ? { keys : false , pattern : route }
94- : parser ( route || "*" , loose ) ;
95-
96- // array destructuring loses keys, so this is done in two steps
97- const result = pattern . exec ( path ) || [ ] ;
98-
99- // when parser is in "loose" mode, `$base` is equal to the
100- // first part of the route that matches the pattern
101- // (e.g. for pattern `/a/:b` and path `/a/1/2/3` the `$base` is `a/1`)
102- // we use this for route nesting
103- const [ $base , ...matches ] = result ;
104-
105- return $base !== undefined
106- ? [
107- true ,
108-
109- ( ( ) => {
110- // for regex paths, `keys` will always be false
111-
112- // an object with parameters matched, e.g. { foo: "bar" } for "/:foo"
113- // we "zip" two arrays here to construct the object
114- // ["foo"], ["bar"] → { foo: "bar" }
115- const groups =
116- keys !== false
117- ? Object . fromEntries ( keys . map ( ( key , i ) => [ key , matches [ i ] ] ) )
118- : result . groups ;
119-
120- // convert the array to an instance of object
121- // this makes it easier to integrate with the existing param implementation
122- let obj = { ...matches } ;
123-
124- // merge named capture groups with matches array
125- groups && Object . assign ( obj , groups ) ;
126-
127- return obj ;
128- } ) ( ) ,
129-
130- // the third value if only present when parser is in "loose" mode,
131- // so that we can extract the base path for nested routes
132- ...( loose ? [ $base ] : [ ] ) ,
133- ]
134- : [ false , null ] ;
95+ route instanceof RegExp ? { pattern : route } : parser ( route || "*" , loose ) ;
96+
97+ const result = pattern . exec ( path ) ;
98+
99+ if ( ! result ) return [ false , null ] ;
100+
101+ // Keep positional captures as well as named params, with named params taking
102+ // precedence (custom parsers can use numeric keys).
103+ const params = { } ;
104+ for ( let i = 1 ; i < result . length ; i ++ ) params [ i - 1 ] = result [ i ] ;
105+ if ( keys ) {
106+ for ( let i = 0 ; i < keys . length ; i ++ ) params [ keys [ i ] ] = result [ i + 1 ] ;
107+ } else {
108+ Object . assign ( params , result . groups ) ;
109+ }
110+
111+ // In loose mode the full match is the base for nested routes:
112+ // pattern `/a/:b` and path `/a/1/2/3` give the base `/a/1`.
113+ return loose ? [ true , params , result [ 0 ] ] : [ true , params ] ;
135114} ;
136115
137- export const useRoute = ( pattern ) =>
138- matchRoute ( useRouter ( ) . parser , pattern , useLocation ( ) [ 0 ] ) ;
116+ export const useRoute = ( pattern ) => {
117+ const router = useRouter ( ) ;
118+ return matchRoute ( router . parser , pattern , usePathnameFromRouter ( router ) ) ;
119+ } ;
139120
140121/*
141122 * Part 2, Low Carb Router API: Router, Route, Link, Switch
@@ -154,9 +135,10 @@ export const Router = ({ children, ...props }) => {
154135 // also, ensure ssrSearch is always defined when ssrPath is provided, so that
155136 // useSearch behavior matches usePathname (proper SSR hydration when client
156137 // renders <Router> without props after server rendered with ssrPath/ssrSearch)
157- const [ path , search = props . ssrSearch ?? "" ] =
158- props . ssrPath ?. split ( "?" ) ?? [ ] ;
159- if ( path ) ( props . ssrSearch = search ) , ( props . ssrPath = path ) ;
138+ if ( props . ssrPath ) {
139+ const [ path , search = props . ssrSearch ?? "" ] = props . ssrPath . split ( "?" ) ;
140+ if ( path ) ( props . ssrSearch = search ) , ( props . ssrPath = path ) ;
141+ }
160142
161143 // hooks can define their own `href` formatter (e.g. for hash location)
162144 props . hrefs = props . hrefs ?? props . hook ?. hrefs ;
@@ -174,7 +156,7 @@ export const Router = ({ children, ...props }) => {
174156 // 2) if the custom `hook` prop is provided, we always inherit from the
175157 // default router instead. this resets all previously overridden options.
176158 // 3) when the router is customized here, it should stay stable between renders
177- let ref = useRef ( { } ) ,
159+ let ref = useRef ( parent ) ,
178160 prev = ref . current ,
179161 next = prev ;
180162
@@ -185,17 +167,16 @@ export const Router = ({ children, ...props }) => {
185167 parent [ k ] + ( props [ k ] ?? "" )
186168 : props [ k ] ?? parent [ k ] ;
187169
188- if ( prev === next && option !== next [ k ] ) {
189- ref . current = next = { ...next } ;
170+ if ( option !== next [ k ] ) {
171+ if ( prev === next ) ref . current = next = { ...next } ;
172+ next [ k ] = option ;
190173 }
191174
192- next [ k ] = option ;
193-
194175 // the new router is no different from the parent or from the memoized value, use parent
195176 if ( option !== parent [ k ] || option !== value [ k ] ) value = next ;
196177 }
197178
198- return h ( RouterCtx . Provider , { value, children } ) ;
179+ return h ( RouterCtx . Provider , { value } , children ) ;
199180} ;
200181
201182const h_route = ( { children, component } , params ) => {
@@ -209,11 +190,12 @@ const h_route = ({ children, component }, params) => {
209190// Cache params object between renders if values are shallow equal
210191const useCachedParams = ( value ) => {
211192 let prev = useRef ( Params0 ) ;
212- const curr = prev . current ;
193+ const curr = prev . current ,
194+ keys = Object . keys ( value ) ;
213195 return ( prev . current =
214196 // Update cache if number of params changed or any value changed
215- Object . keys ( value ) . length !== Object . keys ( curr ) . length ||
216- Object . entries ( value ) . some ( ( [ k , v ] ) => v !== curr [ k ] )
197+ keys . length !== Object . keys ( curr ) . length ||
198+ keys . some ( ( k ) => value [ k ] !== curr [ k ] )
217199 ? value // Return new value if there are changes
218200 : curr ) ; // Return cached value if nothing changed
219201} ;
@@ -240,7 +222,7 @@ export function useSearchParams() {
240222
241223export const Route = ( { path, nest, match, ...renderProps } ) => {
242224 const router = useRouter ( ) ;
243- const [ location ] = useLocationFromRouter ( router ) ;
225+ const location = usePathnameFromRouter ( router ) ;
244226
245227 const [ matches , routeParams , base ] =
246228 // `match` is a special prop to give up control to the parent,
@@ -254,11 +236,13 @@ export const Route = ({ path, nest, match, ...renderProps }) => {
254236
255237 if ( ! matches ) return null ;
256238
257- const children = base
258- ? h ( Router , { base } , h_route ( renderProps , params ) )
259- : h_route ( renderProps , params ) ;
239+ const children = h_route ( renderProps , params ) ;
260240
261- return h ( ParamsCtx . Provider , { value : params , children } ) ;
241+ return h (
242+ ParamsCtx . Provider ,
243+ { value : params } ,
244+ base ? h ( Router , { base } , children ) : children
245+ ) ;
262246} ;
263247
264248export const Link = forwardRef ( ( props , ref ) => {
@@ -319,16 +303,17 @@ export const Link = forwardRef((props, ref) => {
319303 } ) ;
320304} ) ;
321305
322- const flattenChildren = ( children ) =>
323- Array . isArray ( children )
324- ? children . flatMap ( ( c ) =>
325- flattenChildren ( c && c . type === Fragment ? c . props . children : c )
326- )
327- : [ children ] ;
306+ const flattenChildren = ( children , result = [ ] ) => {
307+ if ( Array . isArray ( children ) ) {
308+ for ( const c of children )
309+ flattenChildren ( c && c . type === Fragment ? c . props . children : c , result ) ;
310+ } else result . push ( children ) ;
311+ return result ;
312+ } ;
328313
329314export const Switch = ( { children, location } ) => {
330315 const router = useRouter ( ) ;
331- const [ originalLocation ] = useLocationFromRouter ( router ) ;
316+ const originalLocation = usePathnameFromRouter ( router ) ;
332317
333318 for ( const element of flattenChildren ( children ) ) {
334319 let match = 0 ;
0 commit comments