ref(settings): Replace billing navigation config with a react-hook#115808
Conversation
📊 Type Coverage Diff✅ No new type safety issues introduced. Coverage: 93.56% |
| }); | ||
|
|
||
| setFuzzy(search); | ||
| }, [organization, project, searchOptions]); | ||
| }, [billingNavConfig, organization, project, searchOptions]); | ||
|
|
||
| useEffect(() => void createSearch(), [createSearch]); | ||
|
|
There was a problem hiding this comment.
Bug: The createSearch callback depends on billingNavConfig, which is a new object on every render, causing an infinite render loop when billing data is present.
Severity: HIGH
Suggested Fix
Memoize the result of buildBillingNavigationConfig within the useBillingNavigationConfig hook, likely using useMemo. This will provide a stable object reference for billingNavConfig across re-renders, breaking the infinite loop.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: static/app/components/search/sources/routeSource.tsx#L89-L95
Potential issue: An infinite render loop occurs when subscription data is loaded. The
`useBillingNavigationConfig` hook returns a `billingNavConfig` object that is newly
created on every render. This unstable object reference is used as a dependency in a
`useCallback` hook for the `createSearch` function. Consequently, `createSearch` is
recreated on every render, triggering a `useEffect` that updates component state with a
new `Fuse` instance. This state update causes a re-render, completing the infinite loop
and freezing the component.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
This does sort of sound like a problem lol
There was a problem hiding this comment.
It's not actually infinite rendering in practice, but let me wrap in a use memo
GSBillingNavigationConfig was a null-rendering component whose only purpose was to register 'settings:organization-navigation-config' on HookStore after subscription data became available via componentDidMount. This required OrganizationSettingsNavigation to listen to HookStore for late registrations — an awkward workaround. Replace the whole mechanism with a proper react-hook registered statically at app startup. useBillingNavigationConfig reads from SubscriptionStore via useLegacyStore, so reactivity comes from React state rather than HookStore. OrganizationSettingsNavigation becomes a simple functional component. Removes the 'settings:organization-navigation' and 'settings:organization-navigation-config' hook names entirely.
893d1ca to
8bcb9a5
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8bcb9a5. Configure here.
| const project = useProjectFromSlug({organization, projectSlug: params.projectId}); | ||
| const useBillingNavConfig = | ||
| HookStore.get('react-hook:use-billing-navigation-config') ?? (() => null); | ||
| const billingNavConfig = useBillingNavConfig(); |
There was a problem hiding this comment.
Unstable hook return causes infinite render loop
Medium Severity
The useBillingNavConfig call on every render returns a new object when the registered hook is a plain function (as in the test mock at line 38 of the spec). Since billingNavConfig is included in the useCallback dependency array for createSearch, a new reference each render recreates the callback, re-triggers the useEffect, which calls setFuzzy with a new Fuse instance, causing another render — producing an infinite async render loop. The production hook (useBillingNavigationConfig) mitigates this via useMemo, but the useMemo deps include organization and subscription, which could also become referentially unstable. The PR discussion acknowledges this ("let me wrap in a use memo") but the fix isn't present in this diff.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8bcb9a5. Configure here.


GSBillingNavigationConfigwas a null-rendering class component whose only job was to registersettings:organization-navigation-configonHookStoreincomponentDidMount, after subscription data became available viawithSubscription. This meantOrganizationSettingsNavigationhad to listen toHookStorefor late registrations — an awkward side channel.This PR replaces the whole mechanism with a
react-hook:use-billing-navigation-confighook registered statically at app startup.useBillingNavigationConfigreads fromSubscriptionStoreviauseLegacyStore, so reactivity comes from React state rather than HookStore.OrganizationSettingsNavigationbecomes a simple functional component with no store subscriptions.The
settings:organization-navigationandsettings:organization-navigation-confighook names are removed entirely.