regression(ui-voip): Switching between alternate voice call views breaks widget visibility
Proposed changes (including videos or screenshots)
Follow-up to #41616, from this review comment.
registerView / unregisterView mutated prev.currentViews inside the useState updater. Under StrictMode — which the app runs in (apps/meteor/client/startup/appRoot.tsx) — React calls state updaters twice to surface impure ones, and the result of the second call is the one that lands:
- first call:
prev.currentViews.add(view), returns a new object with a freshfilteredViews; - second call:
prev.currentViews.has(view)is nowtruebecause of the mutation, so it bails out and returnsprev.
State keeps the old object, filteredViews never changes, and consumers reading currentViews (the filtered set) never see the view. So in development registerView('popout') / registerView('widget') were effectively no-ops, and unregisterView had the mirrored problem: the view stayed in the filtered set after the first call deleted it from the raw one.
The updater now copies the set instead of mutating it, and both fields are built by a single withFilteredViews helper so the raw and filtered sets can't drift.
Also removes the comment asking consumers to build a new Set before using it in a useEffect: the exposed set already gets a new identity whenever its contents change, and a new Set(...) on the consumer side would change identity on every render, firing the effect regardless of whether anything changed.
Issue(s)
Steps to test or reproduce
Covered by the new useAvailableViewTracker.spec.ts, which renders the hook inside StrictMode. Against the previous implementation 2 of its 3 cases fail.
Manually, with a call ongoing:
- Open the call in the popout window and close it again — both transitions work.
- Open the room where the call is happening: the widget hides while the room view is registered and comes back when it unregisters.
Further comments
The third test asserts identity stability (same set when nothing changed, new set when it does), since MediaCallInstanceProvider puts currentViews in a useMemo dependency list — a set rebuilt on every update would rebuild the whole context value.
Summary by CodeRabbit
-
Bug Fixes
- Improved view tracking reliability in React StrictMode.
- Prevented duplicate registrations and missing unregistrations from causing unnecessary updates.
- Ensured the widget hides correctly when the room view is registered.
- Preserved view state consistency when available views do not change.
-
Tests
- Added coverage for registration, unregistration, widget visibility, and state reference behavior.