diff --git a/packages/react-router/__tests__/Routes-test.js b/packages/react-router/__tests__/Routes-test.js index 2f14fd745c..00e9222bf0 100644 --- a/packages/react-router/__tests__/Routes-test.js +++ b/packages/react-router/__tests__/Routes-test.js @@ -3,11 +3,15 @@ import { create as createTestRenderer } from 'react-test-renderer'; import { MemoryRouter as Router, Routes, Route } from 'react-router'; describe('A ', () => { - it('renders the first route that matches the URL', () => { - function Home() { - return

Home

; - } + function Home() { + return

Home

; + } + + function Admin() { + return

Admin

; + } + it('renders the first route that matches the URL', () => { let renderer = createTestRenderer( @@ -20,19 +24,11 @@ describe('A ', () => { }); it('does not render a 2nd route that also matches the URL', () => { - function Home() { - return

Home

; - } - - function Dashboard() { - return

Dashboard

; - } - let renderer = createTestRenderer( } /> - } /> + } /> ); @@ -41,10 +37,6 @@ describe('A ', () => { }); it('renders with non-element children', () => { - function Home() { - return

Home

; - } - let renderer = createTestRenderer( @@ -59,14 +51,6 @@ describe('A ', () => { }); it('renders with React.Fragment children', () => { - function Home() { - return

Home

; - } - - function Admin() { - return

Admin

; - } - let renderer = createTestRenderer( @@ -80,4 +64,32 @@ describe('A ', () => { expect(renderer.toJSON()).toMatchSnapshot(); }); + + it('renders route paths prefixed by a basename', () => { + let renderer = createTestRenderer( + + + } /> + + + ); + + expect(renderer.toJSON()).toMatchSnapshot(); + }); + + it('renders routes for a different location', () => { + let location = { + pathname: '/home' + }; + + let renderer = createTestRenderer( + + + } /> + + + ); + + expect(renderer.toJSON()).toMatchSnapshot(); + }); }); diff --git a/packages/react-router/__tests__/__snapshots__/Routes-test.js.snap b/packages/react-router/__tests__/__snapshots__/Routes-test.js.snap index 56c19ccff1..7fc6908ad8 100644 --- a/packages/react-router/__tests__/__snapshots__/Routes-test.js.snap +++ b/packages/react-router/__tests__/__snapshots__/Routes-test.js.snap @@ -6,6 +6,18 @@ exports[`A does not render a 2nd route that also matches the URL 1`] = `; +exports[`A renders route paths prefixed by a basename 1`] = ` +

+ Home +

+`; + +exports[`A renders routes for a different location 1`] = ` +

+ Home +

+`; + exports[`A renders the first route that matches the URL 1`] = `

Home diff --git a/packages/react-router/__tests__/__snapshots__/useRoutes-test.js.snap b/packages/react-router/__tests__/__snapshots__/useRoutes-test.js.snap index d69c903b51..e10b8676f1 100644 --- a/packages/react-router/__tests__/__snapshots__/useRoutes-test.js.snap +++ b/packages/react-router/__tests__/__snapshots__/useRoutes-test.js.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`useRoutes accepts \`basename\` as optional parameter 1`] = ` +

+ Home +

+`; + +exports[`useRoutes accepts \`location\` as optional parameter 1`] = ` +

+ About +

+`; + exports[`useRoutes returns the matching element from a route config 1`] = `

Home diff --git a/packages/react-router/__tests__/useRoutes-test.js b/packages/react-router/__tests__/useRoutes-test.js index 3ee256a860..695bb7e2f9 100644 --- a/packages/react-router/__tests__/useRoutes-test.js +++ b/packages/react-router/__tests__/useRoutes-test.js @@ -3,17 +3,63 @@ import { create as createTestRenderer } from 'react-test-renderer'; import { MemoryRouter as Router, useRoutes } from 'react-router'; describe('useRoutes', () => { + function Home() { + return

Home

; + } + + function About() { + return

About

; + } + it('returns the matching element from a route config', () => { function RoutesRenderer({ routes }) { return useRoutes(routes); } - function Home() { - return

Home

; + let routes = [ + { path: 'home', element: }, + { path: 'about', element: } + ]; + + let renderer = createTestRenderer( + + + + ); + + expect(renderer.toJSON()).toMatchSnapshot(); + }); + + it('accepts `basename` as optional parameter', () => { + function RoutesRenderer({ routes }) { + return useRoutes(routes, { + basename: '/parent' + }); } - function About() { - return

About

; + let routes = [ + { path: 'home', element: }, + { path: 'about', element: } + ]; + + let renderer = createTestRenderer( + + + + ); + + expect(renderer.toJSON()).toMatchSnapshot(); + }); + + it('accepts `location` as optional parameter', () => { + let location = { + pathname: '/about' + }; + + function RoutesRenderer({ routes }) { + return useRoutes(routes, { + location: location + }); } let routes = [ diff --git a/packages/react-router/index.tsx b/packages/react-router/index.tsx index 7c72beb38c..9ac303ba32 100644 --- a/packages/react-router/index.tsx +++ b/packages/react-router/index.tsx @@ -43,11 +43,7 @@ function warning(cond: boolean, message: string): void { } const alreadyWarned: Record = {}; -function warningOnce( - key: string, - cond: boolean, - message: string -) { +function warningOnce(key: string, cond: boolean, message: string) { if (!cond && !alreadyWarned[key]) { alreadyWarned[key] = true; warning(false, message); @@ -332,14 +328,16 @@ if (__DEV__) { */ export function Routes({ basename = '', + location, children }: RoutesProps): React.ReactElement | null { let routes = createRoutesFromChildren(children); - return useRoutes_(routes, basename); + return useRoutes_(routes, { basename, location }); } export interface RoutesProps { basename?: string; + location?: Location; children?: React.ReactNode; } @@ -553,6 +551,11 @@ export function useResolvedPath(to: To): Path { return React.useMemo(() => resolvePath(to, pathname), [to, pathname]); } +interface RoutesOptions { + basename?: string; + location?: Location; +} + /** * Returns the element of the route that matched the current location, prepared * with the correct context to render the remainder of the route tree. Route @@ -563,7 +566,7 @@ export function useResolvedPath(to: To): Path { */ export function useRoutes( partialRoutes: PartialRouteObject[], - basename = '' + { basename = '', location }: RoutesOptions = {} ): React.ReactElement | null { invariant( useInRouterContext(), @@ -576,12 +579,12 @@ export function useRoutes( partialRoutes ]); - return useRoutes_(routes, basename); + return useRoutes_(routes, { basename, location }); } function useRoutes_( routes: RouteObject[], - basename = '' + { basename = '', location }: RoutesOptions = {} ): React.ReactElement | null { let { route: parentRoute, @@ -608,12 +611,13 @@ function useRoutes_( basename = basename ? joinPaths([parentPathname, basename]) : parentPathname; - let location = useLocation() as Location; - let matches = React.useMemo(() => matchRoutes(routes, location, basename), [ - location, - routes, - basename - ]); + let currentLocation = useLocation() as Location; + let usedLocation = location || currentLocation; + + let matches = React.useMemo( + () => matchRoutes(routes, usedLocation, basename), + [location, routes, basename] + ); if (!matches) { // TODO: Warn about nothing matching, suggest using a catch-all route.