Skip to content

fix(tabs): don't throw when removed before firstUpdated() completes (#1472) - #1478

Open
mayuriphad wants to merge 3 commits into
ithaka:developfrom
mayuriphad:fix/tabs-disconnect-before-first-updated
Open

mayuriphad wants to merge 3 commits into
ithaka:developfrom
mayuriphad:fix/tabs-disconnect-before-first-updated

Conversation

@mayuriphad

Copy link
Copy Markdown

This change: (check at least one)

  • Adds a new feature
  • Fixes a bug
  • Improves maintainability
  • Improves documentation
  • Is a release activity

Is this a breaking change? (check one)

  • Yes
  • No

Is the: (complete all)

  • Title of this pull request clear, concise, and indicative of the issue number it addresses, if any?
  • Test suite(s) passing?
  • Code coverage maximal?
  • Changeset added?

What does this change address?
Fixes #1472. PharosTabs.disconnectedCallback() throws TypeError: Cannot read properties of undefined (reading 'disconnect') when the element is removed before its async firstUpdated() has finished.

How does this change work?

  • _tabOverflowObserver and _tabListResizeObserver are only created at the end of firstUpdated(), after it has awaited every tab and panel. They are now typed as optional rather than definitely assigned (!).
  • disconnectedCallback() disconnects them with optional chaining, so a quick removal (for example a framework re-render or a v-if that flips) no longer throws.
  • New test: can be removed before its first update has finished appends a pharos-tabs and removes it straight away.

Additional context
I checked this on my fork's CI with the verify workflow. With only the new test commit, the Test job fails with the TypeError from disconnectedCallback (run). With the fix, Lint and Test both pass (run). Prettier passes with the repo's config. I added a patch changeset for @ithaka/pharos.

Signed-off-by: mayuriphad <mayuriphad656@gmail.com>
The tab overflow and resize observers are created at the end of the
async firstUpdated(), but disconnectedCallback() disconnected them
unconditionally. Removing a pharos-tabs element before firstUpdated()
finished threw 'Cannot read properties of undefined (reading
"disconnect")'. Make the observers optional and disconnect them only
if they exist.

Fixes ithaka#1472

Signed-off-by: mayuriphad <mayuriphad656@gmail.com>
@mayuriphad
mayuriphad requested a review from a team as a code owner October 25, 2026 08:10
@mayuriphad
mayuriphad requested review from daneah, lizlove and satya-achanta-venkata and a lite review from Copilot and removed request for a team October 25, 2026 08:10
@changeset-bot

changeset-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f45fb10

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@ithaka/pharos Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Guard post-await initialization after disconnection and update the regression test to cover the reported timing window.

1 open issue
What changed in this PR

This bug fix prevents pharos-tabs from throwing when disconnected before asynchronous firstUpdated() initialization completes.

Changes:

  • Makes tab observers optional and disconnects them conditionally.
  • Adds regression coverage for early removal.
  • Adds a patch changeset for @ithaka/pharos.
File Summary
packages/​pharos/​src/​components/​tabs/​pharos-tabs.ts Guards observer cleanup, but post-disconnect asynchronous initialization still requires cancellation or a connectivity guard.
packages/​pharos/​src/​components/​tabs/​pharos-tabs.test.ts Adds early-removal coverage, but does not exercise removal while firstUpdated() is suspended on child updates.
.changeset/​tabs-early-disconnect.md Records the patch release for the bug fix.

Review effort: Lite

Comment on lines +140 to +143
// The observers are created at the end of the async firstUpdated(),
// so they don't exist yet if the element is removed before that.
this._tabOverflowObserver?.disconnect();
this._tabListResizeObserver?.disconnect();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, fixed in f45fb10. If the element is removed while firstUpdated() is still awaiting its children, the observers are no longer created for the detached element. When a removed element is attached again, connectedCallback() sets them up, and the setup is now idempotent. The new test removes the element while its first update is pending, checks that no observers exist after the update finishes, and checks that they come back on re-append. Lint and Test pass on my fork's verify run: https://github.com/mayuriphad/pharos/actions/runs/40176986005

…on reconnect

Address review: when the element is removed while firstUpdated() is
still awaiting its children, don't create observers for the detached
element, since disconnectedCallback() has already run and would never
disconnect them. Set them up again in connectedCallback() when a
removed element is attached again, and make the setup idempotent.

Signed-off-by: mayuriphad <mayuriphad656@gmail.com>
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.

Tabs: disconnectedCallback throws if element is removed before firstUpdated completes

2 participants