Closed davidalejandroaguilar closed 4 years ago
Found the issue. I'm opening a PR for this.
I just noticed this is not a documented use pattern. There are 2 documented ways:
data-reflex
as a way to trigger reflexes without writing a Stimulus controller.data-action
to stimulate
the reflex manually within a Stimulus controller.The one I described above was something in the middle:
data-reflex
as a way to trigger reflexes without writing a Stimulus controller. AND using a Stimulus controller to use lifecycle methods.The gotcha here is the little note on the docs that I didn't see before:
If you're calling the stimulate method inside of a Stimulus controller, the event will be emitted by the element the data-controller attribute is declared on.
Which would mean that, for this middle case, it would trigger the wrong lifecycle method (incorrect controller).
Having said that. I believe this PR is still useful for cases like this where you declare a bunch of controllers on the body
or some distant parent; then use data-reflex
so as to not having to stimulate
manually, but being able to use lifecycle methods.
I think this should also remove that note from the doc.
Let me know what you think.
Hi David, I sent you a note on Discord but you didn't stick around to see it, I don't think.
I wanted to thank you for filing such an excellent issue (and PR!) and I am sorry that it can take time to get to these as we're all volunteers. I will say that posting a working clonable repo is :100: and the fastest path to success. Kudos.
I am still working through this but I wanted to offer some code review as I hit items of interest.
First, it's probably easier to work through a series of notifications if you send them to console.log() instead of alert().
Second, I noticed that the reflexes were failing on the server. I created empty create actions so that we could eliminate server issues from the conversation.
Third, you're extending ApplicationController, which has a beforeReflex method. If you define beforeReflex in your controller, you're replacing the original. If you want both of them to run, you have to call the ApplicationController method using super.beforeReflex()
.
I just went through PR #226 and with the minor caveat that we've made the string fragment Reflex
that is in every declarative Reflex optional (so this Regex isn't technically valid) I actually like this PR a lot. @hopsoft has to be the final arbiter of whether this is gonna stick the landing, but conceptually I see what you're trying to do and it does seem like we should make an effort to find the right controller if this situation emerges.
Also, before I get ahead of myself, I want to make it super clear that the SR documentation is an evolving, imperfect attempt to capture a deceptively nuanced collaboration. Not only are we constantly tweaking and adding to the documentation, but I often learn corners of the library by doing my best to write about it. This means that some details can slip through that just aren't helpful or presented in the best way possible. This conversation gives me a few places to improve, because there have been some miscommunications. 😀
Having starred at and played with your sample app a lot over the past few days, I think I finally best know how to explain where things are going wrong. Unfortunately, you are currently confusing "not documented" with "not supported", but because of the specific (clever!) way that you're trying to use it improperly, you're seeing results that don't line up with your expectations.
Today, StimulusReflex is intended to be used in one of the two ways we describe: a declared reflex in the markup, or explicitly calling stimulate. The middle path you're describing isn't a feature we're trying to support. Declared reflexes are perfect for things that don't really need callbacks, although they will work in a simple nesting scenario. And as I said earlier when I expressed support for #226, let's be liberal in what we accept when possible.
That said: if you need callbacks, we strongly advise that you call stimulate()
.
So, what the hell is going on, I feel you asking from here... There's two (maybe three) things that you might not have caught which hopefully demonstrate the method to the madness. The first is that when you put a data-reflex attribute on an element, behind the scenes we're actually putting a Stimulus controller called (wait for it) stimulus-reflex
on the element. So, the element that is emitting the event is the element with the data-reflex attribute, not the controller it's nested under. The reason the Stimulus controllers are receiving events is because SR events bubble up from those declared reflex elements.
The final piece of the puzzle is that stimulate()
accepts an optional node element parameter that is the element the event will emit from (falling back to this.element
). This means that if you put a data-action on the button and called the controller, which called stimulate... the event would come from the controller, not the button.... unless you explicitly specified that the button (or some other element!) should emit it, instead.
To summarize, your other/test controllers are seeing bubbled events because you're currently attempting to make SR work in a way that we didn't intend. You can throw listeners on the data-reflex elements and see for yourself. You can technically use these events if they suit you, but it wasn't what we had in mind and we can't explicitly guarantee that future versions of the library will maintain this behaviour (though you're probably okay?!)
And meanwhile, you did point out a legit way that we could probably improve our edge case handling, and I hope it works out.
Does this all make sense?
Thank you very much for your response and time @leastbad.
console.log()
is better for this. I just wanted to be loud on the sample repo 😅 .beforeReflex
on the ApplicationController
was intentional to show that it was supposed to call the local controller. Sorry I wasn't more clear about that.Yes it makes sense. Last night I figured out that I was using it in an unsupported, rather an undocumented way. Your explanation made it click even more, so thanks for that!
I agree that this would be an edge case since there is a correct way of doing things that would prevent this from happening. I guess the PR could help us prevent this and maybe future edge cases related to this, but I'm not sure if the extra loops would be worth it since we're not trying to offer any guarantees around this specific use case.
In the meantime, do you think we should close this issue and leave the PR open for discussion?
Awesome! I really wanted you to know that you've been properly heard. I thought through everything I said because there's always the chance that we were wrong when we decided how this should work.
Anyhow, if we're settled here, please do close the issue. I'm not in the habit of closing other folks' issues. 👼
As for the PR, let's fix up that Regex and then hopefully @hopsoft loves what he sees!
Bug Report
Describe the bug
When you setup two reflexes within the same container using the Stimulus
data-controller
attribute like the docs example, only the first declared reflex lifecycle methods will get called.In the HTML below, if you click on the
TestReflex
button, you will trigger theOtherReflex
lifecycle method.By the way, I encountered this when declaring some generic reflexes / controllers on the
body
tag, e.g.<body data-controller="debounced ui">
. I wanted to use lifecycle methods on one of them but they were not getting called.To Reproduce
ApplicationController
like this:Expected behavior
OtherReflex
button triggersOtherReflex
.TestReflex
button triggersTestReflex
.Screenshots or reproduction
Video: https://imgur.com/a/mwYRYMB
Repo https://github.com/davidalejandroaguilar/reflex_bug
Versions
StimulusReflex
External tools
Browser