From b48bc087052c2447116088582020cffbeb2f5cd3 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 5 Jul 2023 17:05:07 +0100 Subject: [PATCH] fix(react): Add support for `basename` option of `createBrowserRouter` (#8457) Passes `basename` option to `matchRoutes` we use to generate branches correctly, while updating `pageload` transactions and starting `navigation` transactions. Co-authored-by: Francesco Novy --- packages/react/src/reactrouterv6.tsx | 22 ++++++++--- packages/react/src/types.ts | 8 +++- packages/react/test/reactrouterv6.4.test.tsx | 39 ++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index bc917ff12508..161c7dd5fb3b 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -119,8 +119,15 @@ function getNormalizedName( return [location.pathname, 'url']; } -function updatePageloadTransaction(location: Location, routes: RouteObject[], matches?: AgnosticDataRouteMatch): void { - const branches = Array.isArray(matches) ? matches : (_matchRoutes(routes, location) as unknown as RouteMatch[]); +function updatePageloadTransaction( + location: Location, + routes: RouteObject[], + matches?: AgnosticDataRouteMatch, + basename?: string, +): void { + const branches = Array.isArray(matches) + ? matches + : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]); if (activeTransaction && branches) { activeTransaction.setName(...getNormalizedName(routes, location, branches)); @@ -132,8 +139,9 @@ function handleNavigation( routes: RouteObject[], navigationType: Action, matches?: AgnosticDataRouteMatch, + basename?: string, ): void { - const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location); + const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP') && branches) { if (activeTransaction) { @@ -254,15 +262,17 @@ export function wrapCreateBrowserRouter< TRouter extends Router = Router, >(createRouterFunction: CreateRouterFunction): CreateRouterFunction { // `opts` for createBrowserHistory and createMemoryHistory are different, but also not relevant for us at the moment. + // `basename` is the only option that is relevant for us, and it is the same for all. // eslint-disable-next-line @typescript-eslint/no-explicit-any - return function (routes: RouteObject[], opts?: any): TRouter { + return function (routes: RouteObject[], opts?: Record & { basename?: string }): TRouter { const router = createRouterFunction(routes, opts); + const basename = opts && opts.basename; // The initial load ends when `createBrowserRouter` is called. // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. if (router.state.historyAction === 'POP' && activeTransaction) { - updatePageloadTransaction(router.state.location, routes); + updatePageloadTransaction(router.state.location, routes, undefined, basename); } router.subscribe((state: RouterState) => { @@ -273,7 +283,7 @@ export function wrapCreateBrowserRouter< (state.historyAction === 'PUSH' || state.historyAction === 'POP') && activeTransaction ) { - handleNavigation(location, routes, state.historyAction); + handleNavigation(location, routes, state.historyAction, undefined, basename); } }); diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 409a0a33c23d..fb0652acb036 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -75,7 +75,11 @@ export type UseNavigationType = () => Action; export type RouteObjectArrayAlias = any; export type RouteMatchAlias = any; export type CreateRoutesFromChildren = (children: JSX.Element[]) => RouteObjectArrayAlias; -export type MatchRoutes = (routes: RouteObjectArrayAlias, location: Location) => RouteMatchAlias[] | null; +export type MatchRoutes = ( + routes: RouteObjectArrayAlias, + location: Location, + basename?: string, +) => RouteMatchAlias[] | null; // Types for react-router >= 6.4.2 export type ShouldRevalidateFunction = (args: any) => boolean; @@ -203,7 +207,7 @@ export declare enum HistoryAction { export interface RouterState { historyAction: Action | HistoryAction | any; - location: any; + location: Location; } export interface Router { state: TState; diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index a01da694ce2f..e6bfc67b9bcf 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -265,5 +265,44 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockSetName).toHaveBeenLastCalledWith('/about/:page', 'route'); }); + + it('works with `basename` option', () => { + const [mockStartTransaction] = createInstrumentation(); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + children: [ + { + path: 'us', + element:
Us
, + }, + ], + }, + ], + { + initialEntries: ['/app'], + basename: '/app', + }, + ); + + // @ts-ignore router is fine + render(); + + expect(mockStartTransaction).toHaveBeenCalledTimes(2); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/app/about/us', + op: 'navigation', + tags: { 'routing.instrumentation': 'react-router-v6' }, + metadata: { source: 'url' }, + }); + }); }); });