fix(billing): stop AddMembersDrawer crashing when members are absent - #113
Open
nafees87n wants to merge 1 commit into
Open
fix(billing): stop AddMembersDrawer crashing when members are absent#113nafees87n wants to merge 1 commit into
nafees87n wants to merge 1 commit into
Conversation
nafees87n
force-pushed
the
fix/billing-team-members-selector-crash
branch
from
August 25, 2026 11:50
162dc28 to
5e2d76d
Compare
`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
force-pushed
the
fix/billing-team-members-selector-crash
branch
from
August 25, 2026 12:07
5e2d76d to
261fa5b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes WEB-APP-21NJ —
TypeError: 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
getBillingTeamMembersreturnsundefinedwhenever the members for a team have not been dispatched — only the missing-billingIdcase returns{}, and the declaredRecord<string, any>return type hid it.AddMembersDrawer.tsx:45then doesObject.values(billingTeamMembers)and throws.It was the only consumer that didn't guard.
BillingTeamMembers/index.tsx:57,OtherBillingTeamDetails/index.tsx:27andAddMembersTableActions.tsx:23all already handle the undefined.How the store key ends up absent
useBillingTeamsListeneronly dispatches on a truthy fetch result, andgetBillingTeamMembersProfileyields a falsy value on three paths — a callable error,success: false, and the one seen in production:billing-getMembersProfileansweringsuccess: truewith an undefined payload, becausegetMemberProfilesreturnsnullfor a missing billing team document andbillingTeamMembersProfile?.reduce(...)then yieldsundefined. The frontend'sif (!res.data.success)guard passes andundefinedpropagates.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 toRecord<string, any> | undefinedso 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:
BillingTeamMembers/index.tsx:392andOtherBillingTeamDetails/index.tsx:210both render<Table loading={!billingTeamMembers} />.!undefinedistrue(spinner);!{}isfalse, so the tables would flash an empty "No data" state instead of a spinner until members arrive.useSelectorreference 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
requestly-cloudsrc/modules/billing/api/getBillingTeamMembers.ts:36-43should returnsuccess: falsewhengetMemberProfilesyieldsnull, so a client can distinguish "no such team" from "team with zero members". This PR stops the crash regardless.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-check— does not pass, and does not onmastereither: exit 2 with 1295 pre-existingerror TSlines. What this change is verified against is that it adds zero new ones — the sortederror sets for this branch and
origin/masterare identical apart from one line-number shift caused bythe removed line. CI does not run
type-check, so the widened return type is documentation rather thanan 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 eslinton both changed files — clean🤖 Generated with Claude Code