Skip to content

Commit 1da6ad8

Browse files
authored
feat(react): Support useRoutes hook of React Router 6 (#5624)
This PR introduces `wrapUseRoutes` function, which can be used to patch [`useRoutes` hook](https://reactrouter.com/en/main/hooks/use-routes) of React Router 6.
1 parent 7846163 commit 1da6ad8

File tree

3 files changed

+549
-197
lines changed

3 files changed

+549
-197
lines changed

packages/react/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ 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 } from './reactrouterv6';
10+
export { reactRouterV6Instrumentation, withSentryReactRouterV6Routing, wrapUseRoutes } from './reactrouterv6';

packages/react/src/reactrouterv6.tsx

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type Params<Key extends string = string> = {
2020
readonly [key in Key]: string | undefined;
2121
};
2222

23+
type UseRoutes = (routes: RouteObject[], locationArg?: Partial<Location> | string) => React.ReactElement | null;
24+
2325
// https://github.com/remix-run/react-router/blob/9fa54d643134cd75a0335581a75db8100ed42828/packages/react-router/lib/router.ts#L114-L134
2426
interface RouteMatch<ParamKey extends string = string> {
2527
/**
@@ -141,6 +143,45 @@ function getNormalizedName(
141143
return [location.pathname, 'url'];
142144
}
143145

146+
function updatePageloadTransaction(location: Location, routes: RouteObject[]): void {
147+
if (activeTransaction) {
148+
const [name, source] = getNormalizedName(routes, location, _matchRoutes);
149+
activeTransaction.setName(name);
150+
activeTransaction.setMetadata({ source });
151+
}
152+
}
153+
154+
function handleNavigation(
155+
location: Location,
156+
routes: RouteObject[],
157+
navigationType: Action,
158+
isBaseLocation: boolean,
159+
): void {
160+
if (isBaseLocation) {
161+
if (activeTransaction) {
162+
activeTransaction.finish();
163+
}
164+
165+
return;
166+
}
167+
168+
if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP')) {
169+
if (activeTransaction) {
170+
activeTransaction.finish();
171+
}
172+
173+
const [name, source] = getNormalizedName(routes, location, _matchRoutes);
174+
activeTransaction = _customStartTransaction({
175+
name,
176+
op: 'navigation',
177+
tags: SENTRY_TAGS,
178+
metadata: {
179+
source,
180+
},
181+
});
182+
}
183+
}
184+
144185
export function withSentryReactRouterV6Routing<P extends Record<string, any>, R extends React.FC<P>>(Routes: R): R {
145186
if (
146187
!_useEffect ||
@@ -169,39 +210,12 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
169210
routes = _createRoutesFromChildren(props.children);
170211
isBaseLocation = true;
171212

172-
if (activeTransaction) {
173-
const [name, source] = getNormalizedName(routes, location, _matchRoutes);
174-
activeTransaction.setName(name);
175-
activeTransaction.setMetadata({ source });
176-
}
177-
213+
updatePageloadTransaction(location, routes);
178214
// eslint-disable-next-line react-hooks/exhaustive-deps
179215
}, [props.children]);
180216

181217
_useEffect(() => {
182-
if (isBaseLocation) {
183-
if (activeTransaction) {
184-
activeTransaction.finish();
185-
}
186-
187-
return;
188-
}
189-
190-
if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP')) {
191-
if (activeTransaction) {
192-
activeTransaction.finish();
193-
}
194-
195-
const [name, source] = getNormalizedName(routes, location, _matchRoutes);
196-
activeTransaction = _customStartTransaction({
197-
name,
198-
op: 'navigation',
199-
tags: SENTRY_TAGS,
200-
metadata: {
201-
source,
202-
},
203-
});
204-
}
218+
handleNavigation(location, routes, navigationType, isBaseLocation);
205219
}, [props.children, location, navigationType, isBaseLocation]);
206220

207221
isBaseLocation = false;
@@ -217,3 +231,42 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
217231
// will break advanced type inference done by react router params
218232
return SentryRoutes;
219233
}
234+
235+
export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
236+
if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes || !_customStartTransaction) {
237+
__DEBUG_BUILD__ &&
238+
logger.warn(
239+
'reactRouterV6Instrumentation was unable to wrap `useRoutes` because of one or more missing parameters.',
240+
);
241+
242+
return origUseRoutes;
243+
}
244+
245+
let isBaseLocation: boolean = false;
246+
247+
return (routes: RouteObject[], location?: Partial<Location> | string): React.ReactElement | null => {
248+
const SentryRoutes: React.FC<unknown> = (props: unknown) => {
249+
const Routes = origUseRoutes(routes, location);
250+
251+
const locationArgObject = typeof location === 'string' ? { pathname: location } : location;
252+
const locationObject = (locationArgObject as Location) || _useLocation();
253+
const navigationType = _useNavigationType();
254+
255+
_useEffect(() => {
256+
isBaseLocation = true;
257+
258+
updatePageloadTransaction(locationObject, routes);
259+
}, [props]);
260+
261+
_useEffect(() => {
262+
handleNavigation(locationObject, routes, navigationType, isBaseLocation);
263+
}, [props, locationObject, navigationType, isBaseLocation]);
264+
265+
isBaseLocation = false;
266+
267+
return Routes;
268+
};
269+
270+
return <SentryRoutes />;
271+
};
272+
}

0 commit comments

Comments
 (0)