-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(react): Add TanStack Router integration #12095
Conversation
Thanks again for doing this! I'll need some time to catch up on things with our v8 release but I'll make sure that this makes it in! |
packages/react/src/tanstackrouter.ts
Outdated
const initPathName = WINDOW && WINDOW.location && WINDOW.location.pathname; | ||
if (instrumentPageLoad && initPathName) { | ||
startBrowserTracingPageLoadSpan(client, { | ||
name: initPathName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there no way for us to get the routeId
for the pageload? We can read this from router.state
also right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I believe so. Sorry, I missed putting it there. @lforst can you put that in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Route ID is on each match. You'll probably want the deepest one:
matches[matches.length - 1].routeId
- Entire route config is available at:
router.routesById[routeId]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did the matches[matches.length - 1].routeId
approach below, just forgot to put it here as well.
Hi @MicahLyle, this thread on Tanstack discord could help you here https://discord.com/channels/719702312431386674/1240064824021483570 |
packages/react/src/tanstackrouter.ts
Outdated
export function tanstackRouterInstrumentNavigation(router: TanstackRouter, client: Client): void { | ||
router.history.subscribe(() => { | ||
const state = router.state; | ||
const matches = state.pendingMatches ?? state.matches; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems like what we do internally is more like this:
const matches = [
...state.cachedMatches,
...(state.pendingMatches ?? []),
...state.matches,
]
I did find multiple implementations of this with different spreading order, so I'm not sure which one would be correct 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want one of these depending on the situation:
state.pendingMatches || state.matches
- The user may be in the process of navigating to a new location, which is why you would want pendingMatches to represent that pending navigationstate.matches
- If you're only interested in where the user is currently and are not interested in any potential pending navigation destinations, then this should suffice.
I recommend the former. Do not use state.cachedMatches
for anything outside of reporting back stuff that's cached.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tannerlinsley I did const matches = state.pendingMatches ?? state.matches;
. Would that be worse than state.pendingMatches || state.matches
?
packages/react/src/tanstackrouter.ts
Outdated
} | ||
|
||
export function tanstackRouterInstrumentNavigation(router: TanstackRouter, client: Client): void { | ||
router.history.subscribe(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about the scope, but do we not need to unsubscribe
at one point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can add one, we just need to hook it onto closing the Sentry client, which we currently don't have an exposed hook for in the public API.
Ok, I got this into a minimal working state and added some tests. Not 100% sure on the implementation but I think it should work correctly. One problem I was facing is that we couldn't just straight up use the I also dumped the route params onto the span as attributes. @AbhiPrasad would appreciate another pass! |
packages/react/src/tanstackrouter.ts
Outdated
|
||
// The following types are vendored types from TanStack Router, so we don't have to depend on the actual package | ||
|
||
export interface VendoredTanstackRouter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's move this into a separate file and copy over license as well, I think we need to give attribution.
packages/react/src/tanstackrouter.ts
Outdated
|
||
const paramAttributes: Record<string, string> = {}; | ||
for (const key of Object.keys(match.params)) { | ||
paramAttributes[`params.${key}`] = match.params[key]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we only set this with sendDefaultPii
?
Also, I think we should call this url.path.params.X
because the OTEL attribute for path is url.path
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we only set this with sendDefaultPii?
I would opt against it since all of the params are inside the URL (which we send by default) anyhow.
If for some reason, we would be sending additional data after a patch, I would guard it behind sendDefaultPii.
Also, I think we should call this url.path.params.X because the OTEL attribute for path is url.path
👍
"@vitejs/plugin-react-swc": "^3.5.0", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.2.0", | ||
"@playwright/test": "^1.41.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"@playwright/test": "^1.41.1" | |
"@playwright/test": "^1.41.1", | |
"@sentry-internal/event-proxy-server": "link:../../../event-proxy-server" |
I think this is missing here...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's odd but it doesn't seem to matter. I think because the package is available in the root node_modules. Still gonna add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think that's why it works, we should still add it for safety/correctness I guess :D
Before submitting a pull request, please take a look at our
Contributing guidelines and verify:
yarn lint
) & (yarn test
).Example Repo I Worked Off Of: https://github.com/MicahLyle/tanstack-router-sentry-exploration
Potential Notes/TODOs/Things to Address
subscribe(...)
.const matches = state.pendingMatches ?? state.matches;
is the right approach, or if I should bail immediately ifpendingMatches
is not present. @tannerlinsley any input here?if (instrumentPageLoad && initPathName)
code from the React Router v6 tracing code. It seemed like something we'd want for an SPA: There's only a page load on the initial load and then from there we have only navigation instrumentation. Correct me if I'm wrong though.state
. I kind of hinted at that by adding some additional type properties that are not currently used, but may end up being useful as this integration is expanded.