Skip to content

Commit 7bb9535

Browse files
ShaneKIonitron
andauthored
fix(tabs): respect stencil lifecycle order for tab selection (#30702)
Issue number: resolves #30611 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Currently, the way tabs are set in the tab bar abuses a bug that existed in older versions of Stencil where children would be rendered out of the correct order. This worked in the tab and tab bar's favor previously, but after the fix it broke our implementation so tabs would no longer correctly indicate the selected tab on direct navigation. ## What is the new behavior? We had a temporary fix before we knew what actually caused this issue before, which was basically just a timeout. That blindly worked because it triggered after the child was fully rendered. This change embraces the new, and correct, way these components render and triggers tab updates correctly. ## Does this introduce a breaking change? - [ ] Yes - [X] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Current dev build: ``` 8.7.6-dev.11759345401.165fca78 ``` --------- Co-authored-by: ionitron <[email protected]>
1 parent 3b80473 commit 7bb9535

File tree

4 files changed

+45
-29
lines changed

4 files changed

+45
-29
lines changed

core/src/components/tab-bar/tab-bar.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { TabBarChangedEventDetail } from './tab-bar-interface';
2222
})
2323
export class TabBar implements ComponentInterface {
2424
private keyboardCtrl: KeyboardController | null = null;
25+
private didLoad = false;
2526

2627
@Element() el!: HTMLElement;
2728

@@ -40,6 +41,12 @@ export class TabBar implements ComponentInterface {
4041
@Prop() selectedTab?: string;
4142
@Watch('selectedTab')
4243
selectedTabChanged() {
44+
// Skip the initial watcher call that happens during component load
45+
// We handle that in componentDidLoad to ensure children are ready
46+
if (!this.didLoad) {
47+
return;
48+
}
49+
4350
if (this.selectedTab !== undefined) {
4451
this.ionTabBarChanged.emit({
4552
tab: this.selectedTab,
@@ -65,8 +72,19 @@ export class TabBar implements ComponentInterface {
6572
*/
6673
@Event() ionTabBarLoaded!: EventEmitter<void>;
6774

68-
componentWillLoad() {
69-
this.selectedTabChanged();
75+
componentDidLoad() {
76+
this.ionTabBarLoaded.emit();
77+
// Set the flag to indicate the component has loaded
78+
// This allows the watcher to emit changes from this point forward
79+
this.didLoad = true;
80+
81+
// Emit the initial selected tab after the component is fully loaded
82+
// This ensures all child components (ion-tab-button) are ready
83+
if (this.selectedTab !== undefined) {
84+
this.ionTabBarChanged.emit({
85+
tab: this.selectedTab,
86+
});
87+
}
7088
}
7189

7290
async connectedCallback() {
@@ -90,10 +108,6 @@ export class TabBar implements ComponentInterface {
90108
}
91109
}
92110

93-
componentDidLoad() {
94-
this.ionTabBarLoaded.emit();
95-
}
96-
97111
render() {
98112
const { color, translucent, keyboardVisible } = this;
99113
const mode = getIonMode(this);
-36 Bytes
Loading
-47 Bytes
Loading

core/src/components/tabs/tabs.tsx

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -65,32 +65,33 @@ export class Tabs implements NavOutlet {
6565
this.ionNavWillLoad.emit();
6666
}
6767

68-
componentWillRender() {
68+
componentDidLoad() {
69+
this.updateTabBar();
70+
}
71+
72+
componentDidUpdate() {
73+
this.updateTabBar();
74+
}
75+
76+
private updateTabBar() {
6977
const tabBar = this.el.querySelector('ion-tab-bar');
70-
if (tabBar) {
71-
let tab = this.selectedTab ? this.selectedTab.tab : undefined;
72-
73-
// Fallback: if no selectedTab is set but we're using router mode,
74-
// determine the active tab from the current URL. This works around
75-
// timing issues in React Router integration where setRouteId may not
76-
// be called in time for the initial render.
77-
// TODO(FW-6724): Remove this with React Router upgrade
78-
if (!tab && this.useRouter && typeof window !== 'undefined') {
79-
const currentPath = window.location.pathname;
80-
const tabButtons = this.el.querySelectorAll('ion-tab-button');
81-
82-
// Look for a tab button that matches the current path pattern
83-
for (const tabButton of tabButtons) {
84-
const tabId = tabButton.getAttribute('tab');
85-
if (tabId && currentPath.includes(tabId)) {
86-
tab = tabId;
87-
break;
88-
}
89-
}
90-
}
78+
if (!tabBar) {
79+
return;
80+
}
9181

92-
tabBar.selectedTab = tab;
82+
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
83+
84+
// If tabs has no selected tab but tab-bar already has a selected-tab set,
85+
// don't overwrite it. This handles cases where tab-bar is used without ion-tab elements.
86+
if (tab === undefined) {
87+
return;
88+
}
89+
90+
if (tabBar.selectedTab === tab) {
91+
return;
9392
}
93+
94+
tabBar.selectedTab = tab;
9495
}
9596

9697
/**
@@ -162,6 +163,7 @@ export class Tabs implements NavOutlet {
162163
this.selectedTab = selectedTab;
163164
this.ionTabsWillChange.emit({ tab: selectedTab.tab });
164165
selectedTab.active = true;
166+
this.updateTabBar();
165167
return Promise.resolve();
166168
}
167169

0 commit comments

Comments
 (0)