Skip to content

Commit fe0b32f

Browse files
fix(segment-button): protect connectedCallback for when segment-content has not yet been created (#30133)
Issue number: internal --------- <!-- 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. --> When the `connectedCallback` method is called for a segment-button and its corresponding segment-content has not been created in that instant, a console error is thrown and the method returns. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - `connectedCallback` will now wait, at most 1 second, for the corresponding segment-content to be created. - The new behaviour can be tested in segment-view/basic. ## 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. -->
1 parent b0c5555 commit fe0b32f

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

core/src/components/segment-button/segment-button.tsx

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,52 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
6767
this.updateState();
6868
}
6969

70-
connectedCallback() {
70+
private getNextSiblingOfType<T extends Element>(element: Element): T | null {
71+
let sibling = element.nextSibling;
72+
while (sibling) {
73+
if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as T) !== null) {
74+
return sibling as T;
75+
}
76+
sibling = sibling.nextSibling;
77+
}
78+
return null;
79+
}
80+
81+
private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise<HTMLElement> {
82+
return new Promise((resolve, reject) => {
83+
let timeoutId: NodeJS.Timeout | undefined = undefined;
84+
let animationFrameId: number;
85+
86+
const check = () => {
87+
if (!ionSegment) {
88+
reject(new Error(`Segment not found when looking for Segment Content`));
89+
return;
90+
}
91+
92+
const segmentView = this.getNextSiblingOfType<HTMLIonSegmentViewElement>(ionSegment); // Skip the text nodes
93+
const segmentContent = segmentView?.querySelector(
94+
`ion-segment-content[id="${contentId}"]`
95+
) as HTMLIonSegmentContentElement | null;
96+
if (segmentContent) {
97+
clearTimeout(timeoutId); // Clear the timeout if the segmentContent is found
98+
cancelAnimationFrame(animationFrameId);
99+
resolve(segmentContent);
100+
} else {
101+
animationFrameId = requestAnimationFrame(check); // Keep checking on the next animation frame
102+
}
103+
};
104+
105+
check();
106+
107+
// Set a timeout to reject the promise
108+
timeoutId = setTimeout(() => {
109+
cancelAnimationFrame(animationFrameId);
110+
reject(new Error(`Unable to find Segment Content with id="${contentId} within 1000 ms`));
111+
}, 1000);
112+
});
113+
}
114+
115+
async connectedCallback() {
71116
const segmentEl = (this.segmentEl = this.el.closest('ion-segment'));
72117
if (segmentEl) {
73118
this.updateState();
@@ -78,12 +123,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
78123
// Return if there is no contentId defined
79124
if (!this.contentId) return;
80125

81-
// Attempt to find the Segment Content by its contentId
82-
const segmentContent = document.getElementById(this.contentId) as HTMLIonSegmentContentElement | null;
83-
84-
// If no associated Segment Content exists, log an error and return
85-
if (!segmentContent) {
86-
console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`);
126+
let segmentContent;
127+
try {
128+
// Attempt to find the Segment Content by its contentId
129+
segmentContent = await this.waitForSegmentContent(segmentEl, this.contentId);
130+
} catch (error) {
131+
// If no associated Segment Content exists, log an error and return
132+
console.error('Segment Button: ', (error as Error).message);
87133
return;
88134
}
89135

core/src/components/segment-view/test/basic/index.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@
123123
<button class="expand" onClick="changeSegmentContent()">Change Segment Content</button>
124124

125125
<button class="expand" onClick="clearSegmentValue()">Clear Segment Value</button>
126+
127+
<button class="expand" onClick="addSegmentButtonAndContent()">Add New Segment Button & Content</button>
126128
</ion-content>
127129

128130
<ion-footer>
@@ -158,6 +160,34 @@
158160
segment.value = undefined;
159161
});
160162
}
163+
164+
async function addSegmentButtonAndContent() {
165+
const segment = document.querySelector('ion-segment');
166+
const segmentView = document.querySelector('ion-segment-view');
167+
168+
const newButton = document.createElement('ion-segment-button');
169+
const newId = `new-${Date.now()}`;
170+
newButton.setAttribute('content-id', newId);
171+
newButton.setAttribute('value', newId);
172+
newButton.innerHTML = '<ion-label>New Button</ion-label>';
173+
174+
segment.appendChild(newButton);
175+
176+
setTimeout(() => {
177+
// Timeout to test waitForSegmentContent() in segment-button
178+
const newContent = document.createElement('ion-segment-content');
179+
newContent.setAttribute('id', newId);
180+
newContent.innerHTML = 'New Content';
181+
182+
segmentView.appendChild(newContent);
183+
184+
// Necessary timeout to ensure the value is set after the content is added.
185+
// Otherwise, the transition is unsuccessful and the content is not shown.
186+
setTimeout(() => {
187+
segment.setAttribute('value', newId);
188+
}, 200);
189+
}, 200);
190+
}
161191
</script>
162192
</ion-app>
163193
</body>

0 commit comments

Comments
 (0)