-
Notifications
You must be signed in to change notification settings - Fork 26
fix(ktoaster): prevent creating duplicate containers [KHCP-18535] #3017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(ktoaster): prevent creating duplicate containers [KHCP-18535] #3017
Conversation
✅ Deploy Preview for kongponents-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for kongponents ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
hey @portikM 👋 , sorry I know you are up the wall with other stuff at the moment, if you get a sec could you add a bit of detail in the description here what the "issues introduced" were exactly, thanks!
It looks like this PR is just a direct copy of the reverted one, and from that I guess you are planning on working on top of the reverted one is that understanding correct? |
Hi @johncowen! Yes I had to deprioritize this for a bit but I am picking this back up today. The issue with this implementation came up in this thread. I didn't have time to identify exactly why, but essentially the toast messages weren't showing sometimes, our regression tests caught it. I still think this approach is good, just need to debug and fix the issue. |
|
Thanks Max! Not sure if this helps, but we had a quick look at this yesterday and noticed that there's an opportunity for unintended behaviour here if you have 2 individual ToastManagers. One example is: Given 2 ToastManagers:
I think the issue is worse on the current code on We found only two instances (in upstream applications aside from kuma) where the toast managers being created actually have destroy called on them. FYI: in kuma, we are not currently calling destroy at all but "ideally" things would get cleaned up properly. I guess if we don't want to go down the lifecycle tracking route, another option would be to just provide the toast manager from a single "shell" application instead of having individual applications instantiate a toast manager themselves. Application would then access the manager provided by the "shell" and the shell would be responsible for cleaning up. Of course if we aren't super precious about cleaning up (after all we want the toast manager to be available throughout the lifetime of the app) we could skip that (ideally we wouldn't skip cleanup, but maybe pragmatism wins here if we don't want to manage lifecycle) This option would solve it for our specific needs, but obvs not for others (not sure how many other folks are encountering this issue with duplicate toast managers) - I guess a "fix" there would be to document that ToastManager should be implemented as a singleton in your application and then destroy should only be called once you are sure all toast managers are "finished" (if at all) to prevent these sorts of shared state issues. Anyway, thought I'd share our findings/thoughts from yesterday, none of the above are blocking asks or anything |
Thanks for sharing your findings jc! Yeah I guess once we get this working there is a slight chance of mishap when 1 instance of ToastManager is being destroyed at the exact moment when another dispatches a toast. On the other hand, once this finalized and ToastManager only mounts one |
Yeah this is what we were wondering also 👍 . I guess for us this is fine, but maybe we need to explain this in the docs that if you want to destroy you should only do it when you are sure you application has totally finished (I guess some folks/usecase may want to ensure this happens?). This is a total "maybe we should add this docs" from me, will leave that decision up to you |
|
Ah and we'd also have to communicate and change this in the two places we found it, if you need help finding ping me offline 👍
|
Yeah totally. I was thinking we could add an optional attribute in Update: on the second thought, all |
Yeah agree 👍 We should chase up internally those apps that are calling destroy also. (P.S. I'm still on the side of "ideally" we'd clean up, but considering everything I think this approach is the best way forwards) |
| * Destroys the ToastManager instance and removes the toasters container element from the DOM | ||
| * @param removeToastersContainer - Whether to remove the toasters container element from the DOM (defaults to false) | ||
| */ | ||
| public destroy(removeToastersContainer: boolean = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Konnect where we can have multiple ToastManager on the same view, removing the toasters container element from the DOM can [very unlikely but can] lead to a race condition when one instance removes the container at the same moment as another instance is attempting to dispatch a notification.
Because a toaster instance will not create a new container if one already exists, I think it's no big deal if we just have the toasters container element in the DOM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to cover this in the tests by adding an assertion for making sure that the DOM element is not being removed after calling destroy with the default param (or setting it to false)? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's also a theoretical bug here also.
If I call destroy and then destroy(true) I don't think the DOM container will get cleaned up:
- destroy() >
this.toastersContainer = null - destroy(true)
this.toastersContainer && removeToastersContainer>null && true === false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both good points, pushed a fix for both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably I wasn't clear enough, but there is still a missing test/assertion for the case of calling destroy() and that the container still exists. There is only an assertion that the container is removed after calling destroy(true). 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yup my miss. Added a test scenario for that now.
@johncowen fixed my implementation and added a note for what we talked about in the docs. This PR now ready for review.
|
adamdehaven
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: is there any impact to the Nuxt module and the composable it exposes? (@arashsheyda @portikM )
| /> | ||
| ``` | ||
|
|
||
| <KRadio card v-model="errorPropRadio" label="Input error" error description="Some description text" :selected-value="false" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: This is unrelated but a valid docs example we requested 👍🏼
I would imagine there's none but I can double check. |
|
I don't think it it owuld effect at all, but also if it makes it easier we could setup a mini nuxt sandbox here for easier testing |
| } | ||
|
|
||
| private setupToastersContainer(): void { | ||
| const toastersContainerEl = document?.getElementById(toasterContainerId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you pull the preview PR into portal? Just want to make sure this doesn't init on the server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
johncowen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing as I've been involved in the convo a little here this LGTM! (considering we are fine not cleaning up of course)
Thanks for the extra detail in the work here Max 👍
Separately and just out of interest (incase you are interested also) in regards to Adam's comment you have left to address, I implemented an improved guard for SSR over in my old suggestion PR over here https://github.com/Kong/kongponents/pull/3045/changes#diff-2adca4150f4c28f9c8c391a0ed2520d130301583843a5bb1890e526f835a8190R106-R113
Obvs we aren't doing that here, but I wanted to see what it would like. Its quite nice that you only have to guard in one place which is out of the main body of code at creation time, and then you can totally forget (and not worry about) the fact that you might be running in SSR everywhere else.
I quite like fixing things in a single place at the root and not having to use ? and if statements everywhere in multiple places, but maybe thats a personal opinion thing 🤷
## [9.49.5](v9.49.4...v9.49.5) (2026-01-13) ### Bug Fixes * **ktoaster:** prevent creating duplicate containers [KHCP-18535] ([#3017](#3017)) ([0f949e8](0f949e8))
|
🎉 This PR is included in version 9.49.5 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Preview package from this PR in consuming applicationIn consuming application project install preview version of kongponents generated by this PR: |

Summary
Addresses: https://konghq.atlassian.net/browse/KHCP-18535
Second attempt at preventing
ToastManagerfrom creating duplicate containers. Fixing issues introduced in #2980 and reverted in #3015.