Skip to content

Commit 7d486d3

Browse files
committed
feat(react): Add tracing support for React Router 6.4 Data API.
1 parent 454fd5d commit 7d486d3

File tree

6 files changed

+560
-80
lines changed

6 files changed

+560
-80
lines changed

packages/react/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@types/history-4": "npm:@types/[email protected]",
3232
"@types/history-5": "npm:@types/[email protected]",
3333
"@types/hoist-non-react-statics": "^3.3.1",
34+
"@types/node-fetch": "^2.6.0",
3435
"@types/react": "^17.0.3",
3536
"@types/react-router-3": "npm:@types/[email protected]",
3637
"@types/react-router-4": "npm:@types/[email protected]",
@@ -39,12 +40,13 @@
3940
"eslint-plugin-react-hooks": "^4.0.8",
4041
"history-4": "npm:[email protected]",
4142
"history-5": "npm:[email protected]",
43+
"node-fetch": "^2.6.0",
4244
"react": "^18.0.0",
4345
"react-dom": "^18.0.0",
4446
"react-router-3": "npm:[email protected]",
4547
"react-router-4": "npm:[email protected]",
4648
"react-router-5": "npm:[email protected]",
47-
"react-router-6": "npm:react-router@6.3.0",
49+
"react-router-6": "npm:react-router@6.4.2",
4850
"redux": "^4.0.5"
4951
},
5052
"scripts": {

packages/react/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ export { ErrorBoundary, withErrorBoundary } from './errorboundary';
77
export { createReduxEnhancer } from './redux';
88
export { reactRouterV3Instrumentation } from './reactrouterv3';
99
export { reactRouterV4Instrumentation, reactRouterV5Instrumentation, withSentryRouting } from './reactrouter';
10-
export { reactRouterV6Instrumentation, withSentryReactRouterV6Routing, wrapUseRoutes } from './reactrouterv6';
10+
export {
11+
reactRouterV6Instrumentation,
12+
withSentryReactRouterV6Routing,
13+
wrapUseRoutes,
14+
wrapCreateBrowserRouter,
15+
} from './reactrouterv6';

packages/react/src/reactrouterv6.tsx

Lines changed: 57 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,68 +7,22 @@ import { getNumberOfUrlSegments, logger } from '@sentry/utils';
77
import hoistNonReactStatics from 'hoist-non-react-statics';
88
import React from 'react';
99

10-
import { Action, Location } from './types';
11-
12-
interface NonIndexRouteObject {
13-
caseSensitive?: boolean;
14-
children?: RouteObject[];
15-
element?: React.ReactNode | null;
16-
index?: false;
17-
path?: string;
18-
}
19-
20-
interface IndexRouteObject {
21-
caseSensitive?: boolean;
22-
children?: undefined;
23-
element?: React.ReactNode | null;
24-
index?: true;
25-
path?: string;
26-
}
27-
28-
// This type was originally just `type RouteObject = IndexRouteObject`, but this was changed
29-
// in https://github.com/remix-run/react-router/pull/9366, which was released with `6.4.2`
30-
// See https://github.com/remix-run/react-router/issues/9427 for a discussion on this.
31-
type RouteObject = IndexRouteObject | NonIndexRouteObject;
32-
33-
type Params<Key extends string = string> = {
34-
readonly [key in Key]: string | undefined;
35-
};
36-
37-
type UseRoutes = (routes: RouteObject[], locationArg?: Partial<Location> | string) => React.ReactElement | null;
38-
39-
// https://github.com/remix-run/react-router/blob/9fa54d643134cd75a0335581a75db8100ed42828/packages/react-router/lib/router.ts#L114-L134
40-
interface RouteMatch<ParamKey extends string = string> {
41-
/**
42-
* The names and values of dynamic parameters in the URL.
43-
*/
44-
params: Params<ParamKey>;
45-
/**
46-
* The portion of the URL pathname that was matched.
47-
*/
48-
pathname: string;
49-
/**
50-
* The portion of the URL pathname that was matched before child routes.
51-
*/
52-
pathnameBase: string;
53-
/**
54-
* The route object that was used to match.
55-
*/
56-
route: RouteObject;
57-
}
58-
59-
type UseEffect = (cb: () => void, deps: unknown[]) => void;
60-
type UseLocation = () => Location;
61-
type UseNavigationType = () => Action;
62-
63-
// For both of these types, use `any` instead of `RouteObject[]` or `RouteMatch[]`.
64-
// Have to do this so we maintain backwards compatability between
65-
// react-router > 6.0.0 and >= 6.4.2.
66-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
67-
type RouteObjectArrayAlias = any;
68-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
69-
type RouteMatchAlias = any;
70-
type CreateRoutesFromChildren = (children: JSX.Element[]) => RouteObjectArrayAlias;
71-
type MatchRoutes = (routes: RouteObjectArrayAlias, location: Location) => RouteMatchAlias[] | null;
10+
import {
11+
Action,
12+
AgnosticDataRouteMatch,
13+
CreateRouterFunction,
14+
CreateRoutesFromChildren,
15+
Location,
16+
MatchRoutes,
17+
RouteMatch,
18+
RouteObject,
19+
Router,
20+
RouterState,
21+
UseEffect,
22+
UseLocation,
23+
UseNavigationType,
24+
UseRoutes,
25+
} from './types';
7226

7327
let activeTransaction: Transaction | undefined;
7428

@@ -122,14 +76,12 @@ export function reactRouterV6Instrumentation(
12276
function getNormalizedName(
12377
routes: RouteObject[],
12478
location: Location,
125-
matchRoutes: MatchRoutes,
79+
branches: RouteMatch[],
12680
): [string, TransactionSource] {
127-
if (!routes || routes.length === 0 || !matchRoutes) {
81+
if (!routes || routes.length === 0) {
12882
return [location.pathname, 'url'];
12983
}
13084

131-
const branches = matchRoutes(routes, location) as unknown as RouteMatch[];
132-
13385
let pathBuilder = '';
13486
if (branches) {
13587
// eslint-disable-next-line @typescript-eslint/prefer-for-of
@@ -167,9 +119,11 @@ function getNormalizedName(
167119
return [location.pathname, 'url'];
168120
}
169121

170-
function updatePageloadTransaction(location: Location, routes: RouteObject[]): void {
171-
if (activeTransaction) {
172-
activeTransaction.setName(...getNormalizedName(routes, location, _matchRoutes));
122+
function updatePageloadTransaction(location: Location, routes: RouteObject[], matches?: AgnosticDataRouteMatch): void {
123+
const branches = Array.isArray(matches) ? matches : (_matchRoutes(routes, location) as unknown as RouteMatch[]);
124+
125+
if (activeTransaction && branches) {
126+
activeTransaction.setName(...getNormalizedName(routes, location, branches));
173127
}
174128
}
175129

@@ -178,6 +132,7 @@ function handleNavigation(
178132
routes: RouteObject[],
179133
navigationType: Action,
180134
isBaseLocation: boolean,
135+
matches?: AgnosticDataRouteMatch,
181136
): void {
182137
if (isBaseLocation) {
183138
if (activeTransaction) {
@@ -187,12 +142,14 @@ function handleNavigation(
187142
return;
188143
}
189144

190-
if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP')) {
145+
const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location);
146+
147+
if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP') && branches) {
191148
if (activeTransaction) {
192149
activeTransaction.finish();
193150
}
194151

195-
const [name, source] = getNormalizedName(routes, location, _matchRoutes);
152+
const [name, source] = getNormalizedName(routes, location, branches);
196153
activeTransaction = _customStartTransaction({
197154
name,
198155
op: 'navigation',
@@ -294,3 +251,32 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
294251
return <SentryRoutes />;
295252
};
296253
}
254+
255+
export function wrapCreateBrowserRouter(createRouterFunction: CreateRouterFunction): CreateRouterFunction {
256+
// `opts` for createBrowserHistory and createMemoryHistory are different, but also not relevant for us at the moment.
257+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
258+
return function (routes: RouteObject[], opts?: any): Router {
259+
const router = createRouterFunction(routes, opts);
260+
261+
// The initial load ends when `createBrowserRouter` is called.
262+
// This is the earliest convenient time to update the transaction name.
263+
// Callbacks to `router.subscribe` are not called for the initial load.
264+
if (router.state.historyAction === 'POP' && activeTransaction) {
265+
updatePageloadTransaction(router.state.location, routes);
266+
}
267+
268+
router.subscribe((state: RouterState) => {
269+
const location = state.location;
270+
271+
if (
272+
_startTransactionOnLocationChange &&
273+
(state.historyAction === 'PUSH' || state.historyAction === 'POP') &&
274+
activeTransaction
275+
) {
276+
handleNavigation(location, routes, state.historyAction, false);
277+
}
278+
});
279+
280+
return router;
281+
};
282+
}

0 commit comments

Comments
 (0)