Skip to content

ref: move use-button-tracking from sentry hooks to react context #95220

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

Draft
wants to merge 2 commits into
base: tkdodo/ref/decouple-button-tracking
Choose a base branch
from

Conversation

TkDodo
Copy link
Contributor

@TkDodo TkDodo commented Jul 10, 2025

To be able to decouple our core/components from getsentry specific logic, we have to stop using sentry-hooks inside core/components.

However, button tracking logic is defined in getentry and injected via 'react-hook:use-button-tracking', which tracks button click events in sentry itself.

The react idiomatic way to inject specific logic into a “generic” library is “dependency injection” via react context provided by the generic library.

In our case, that means core/components/button exposes a <TrackingContextProvider> that consumers can render and pass a function that will be called when a button is clicked. This is similar to how react-aria allows link behaviour to be injected via their RouterProvider.

This is what I’ve been doing in the base branch:

Now here’s the thing: sentry hooks do pretty much the same thing for us as react context - at least for things that we only use within react. But that means we’ve now created another layer of indirection. Basically, the flow is:

HookStore --> React Context --> Button

the sentry specific SentryTrackingProvider basically takes the value we’ve put into the hook-store and forwards it to the agnostic TrackingContextProvider coming from our core components:

export function SentryTrackingProvider({children}: {children: React.ReactNode}) {
  const useButtonTracking = HookStore.get('react-hook:use-button-tracking')[0];
  const trackingContextValue = useMemo(() => ({useButtonTracking}), [useButtonTracking]);

  return (
    <TrackingContextProvider value={trackingContextValue}>
      {children}
    </TrackingContextProvider>
  );
}

This layer of indirection feels unnecessary, and passing hooks around as values is technically against the rules of react (see never pass hooks around as regular values).


In this PR, I’m trying to remove that indirection and just do:

React Context --> Button

At the top level, where we import('getsentry/registerHooks'), we now not only get back registerHooks, but also a SentryHooksProvider that directly renders sentry+react specific things. Right now, it’s just the TrackingContext, but it could be other things, too:

export function SentryHooksProvider({children}: {children?: React.ReactNode}) {
  const buttonTracking = useButtonTracking();
  const trackingContextValue = useMemo(() => ({buttonTracking}), [buttonTracking]);

  return (
    <TrackingContextProvider value={trackingContextValue}>
      {children}
    </TrackingContextProvider>
  );
}

We then take this Provider and have to drill it down a bunch of levels (this is the main drawback tbh), and render it as a top-level provider in our root route. This is necessary to be able to call hooks like useRouter() inside the custom hooks.

With this, we’d have:

  • one layer to inject functionality into react instead of two.
  • using a standardized system (react context) instead of our own abstraction.
  • calling the useButtonTracking hook directly instead of passing it around as a value
  • removed the react-hook:use-button-tracking sentry hook

If we agree to this approach, I’d like to follow up with removing all sentry hooks starting with react-hook:, and potentially even more sentry hooks that are only used inside react.

Please let me know if I’m missing something, e.g. if those hooks are also used by self-hosted customers to show their own things?

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jul 10, 2025
Comment on lines +2565 to +2566
<Route component={ProvideAriaRouter}>
<Route component={SentryHooksProvider}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd combine the two providers into one component. i think there's some places that look at the route tree in bad ways.

Makes me nervous to change that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would need to be an inline component then because there’s no way to pass props to component. Like this maybe?

<Route
      component={({children}: {children: React.ReactNode}) => {
        return (
          <ProvideAriaRouter>
            <SentryHooksProvider>{children}</SentryHooksProvider>
          </ProvideAriaRouter>
        );
      }}
    >

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that's probably fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Frontend Automatically applied to PRs that change frontend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants