Skip to content

fix(billing): stop AddMembersDrawer crashing when members are absent - #113

Open
nafees87n wants to merge 1 commit into
masterfrom
fix/billing-team-members-selector-crash
Open

fix(billing): stop AddMembersDrawer crashing when members are absent#113
nafees87n wants to merge 1 commit into
masterfrom
fix/billing-team-members-selector-crash

Conversation

@nafees87n

@nafees87n nafees87n commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fixes WEB-APP-21NJTypeError: Cannot convert undefined or null to object, 45 events / 22 users since 10 Aug, still firing. level: fatal, caught by the react-router error boundary, so the whole billing settings page blanks.

What's wrong

getBillingTeamMembers returns undefined whenever the members for a team have not been dispatched — only the missing-billingId case returns {}, and the declared Record<string, any> return type hid it. AddMembersDrawer.tsx:45 then does Object.values(billingTeamMembers) and throws.

It was the only consumer that didn't guard. BillingTeamMembers/index.tsx:57, OtherBillingTeamDetails/index.tsx:27 and AddMembersTableActions.tsx:23 all already handle the undefined.

How the store key ends up absent

useBillingTeamsListener only dispatches on a truthy fetch result, and getBillingTeamMembersProfile yields a falsy value on three paths — a callable error, success: false, and the one seen in production: billing-getMembersProfile answering success: true with an undefined payload, because getMemberProfiles returns null for a missing billing team document and billingTeamMembersProfile?.reduce(...) then yields undefined. The frontend's if (!res.data.success) guard passes and undefined propagates.

Live trigger for that path: CLOUD-FUNCTIONS-2T ([getMemberProfiles] user profile not found in billing team), 133 events in 6 days.

The fix

Guard at the call site in AddMembersDrawer, matching what every other consumer does, and change the selector's return type to Record<string, any> | undefined so a future caller is forced by the compiler to handle absence.

Also fixes a dead fallback: AddMembersDrawer's || [] was applied to the result of .map(), which is never falsy. It's moved to the input where it was meant to be, so the expression stops reading as already-guarded.

Why the selector is NOT defaulted to {}

That was the first version of this PR and it is wrong two ways — both caught before merge:

  1. It destroys two loading states. BillingTeamMembers/index.tsx:392 and OtherBillingTeamDetails/index.tsx:210 both render <Table loading={!billingTeamMembers} />. !undefined is true (spinner); !{} is false, so the tables would flash an empty "No data" state instead of a spinner until members arrive.
  2. It breaks useSelector reference equality. ?? {} allocates a fresh object per call, and react-redux 8.1.3 compares selector output by reference, so both components would re-render on every dispatched action app-wide for as long as members are absent.

The runtime contract is therefore unchanged; only the type and the one unguarded call site change. That's noted in a comment on the selector so the {} default isn't reintroduced later.

Follow-up, not in this PR

  • Backend: requestly-cloud src/modules/billing/api/getBillingTeamMembers.ts:36-43 should return success: false when getMemberProfiles yields null, so a client can distinguish "no such team" from "team with zero members". This PR stops the crash regardless.
  • The two loading={!billingTeamMembers} tables would be better driven by an explicit per-team loading flag than by data absence, but that's a behaviour change and doesn't belong in a crash fix.

Verification

  • npm run type-checkdoes not pass, and does not on master either: exit 2 with 1295 pre-existing
    error TS lines. What this change is verified against is that it adds zero new ones — the sorted
    error sets for this branch and origin/master are identical apart from one line-number shift caused by
    the removed line. CI does not run type-check, so the widened return type is documentation rather than
    an enforced guard. An earlier revision of this PR description claimed a clean exit; that was wrong — the
    exit code had been masked by a pipe to tail.
  • npx eslint on both changed files — clean
  • All four selector consumers reviewed for truthiness and identity dependence

🤖 Generated with Claude Code

@nafees87n
nafees87n force-pushed the fix/billing-team-members-selector-crash branch from 162dc28 to 5e2d76d Compare August 25, 2026 11:50
`AddMembersDrawer.tsx:45` fed `getBillingTeamMembers`' result straight to
`Object.values`, throwing "Cannot convert undefined or null to object" and
blanking the whole billing page via the react-router error boundary. It was the
only consumer that didn't guard the value — `BillingTeamMembers`,
`OtherBillingTeamDetails` and `AddMembersTableActions` all already do.

The selector returns `undefined` whenever the members for a team have not been
dispatched. `useBillingTeamsListener` only dispatches on a truthy fetch result,
and `getBillingTeamMembersProfile` yields a falsy value on a callable error, on
`success: false`, and — the case seen in production — when
`billing-getMembersProfile` answers `success: true` with an undefined
`billingTeamMembers` payload because the billing team document was not found.

Guard at the call site, matching what every other consumer does, and encode the
absence in the selector's return type so a future caller has to handle it.

Deliberately NOT defaulting the selector to `{}`, which was the first attempt
here and is wrong two ways:

  - `BillingTeamMembers/index.tsx:392` and `OtherBillingTeamDetails/index.tsx:210`
    both drive an antd `<Table loading={!billingTeamMembers} />`. A truthy empty
    object makes that `false`, silently replacing the load spinner with an
    empty-state until members arrive.
  - `?? {}` allocates a new object per call, and react-redux 8's `useSelector`
    compares by reference, so the component would re-render on every dispatched
    action for as long as members are absent.

Fixes WEB-APP-21NJ

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nafees87n
nafees87n force-pushed the fix/billing-team-members-selector-crash branch from 5e2d76d to 261fa5b Compare August 25, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant