stimulus: Outlets not available in connect/disconnect
When I try to test Outlets using the example from the docs, I get the following error:
The provided outlet element is missing the outlet controller "result" for "search"
<div class="result" data-controller="result">
I’ve prepared a JSFiddle to demonstrate. Here’s the full code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Outlets tehst</title>
<script type="module">
import { Application, Controller } from 'https://unpkg.com/@hotwired/stimulus/dist/stimulus.js'
const app = Application.start()
app.debug = true;
app.register('search', class extends Controller {
static outlets = ['result']
connect() {
console.log(this.resultOutlets)
this.element.insertAdjacentHTML('beforeend', `. Found ${this.resultOutlets.length} outlets`);
}
});
app.register('result', class extends Controller {});
</script>
</head>
<body>
<div
data-controller="search"
data-search-result-outlet=".result"
>Search</div>
<div>
<div class="result" data-controller="result">Result</div>
<div class="result" data-controller="result">Result</div>
</div>
</body>
</html>
I’ve done a little debugging and found that Router.getContextForElementAndIdentifier finds a module with no contexts (empty Array), which quickly leads to the error above.
I’ve tried versions 3.2.0 and 3.2.1. Neither works.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 6
- Comments: 22 (10 by maintainers)
@dhh could you please create a new release which includes the fix for this issue? Thanks in advance.
I will check out what might be wrong here, thanks for opening this issue @drjayvee!
Thanks for pointing out the workaround.
I’d argue that this is a bug in the implementation. The values/targets/classes APIs don’t require waits, so this is hugely surprising.
The error message also seems totally buggy since its message complains about the controller missing, when the very message points out that the
data-controllerattribute is present and correct.At the very least, the timing issue should be documented.
This seems to be a timing issue. I tried delaying it a bit and got it right. It may be a good idea to modify the sample code in the documentation. https://codepen.io/nazomikan/pen/zYayvNJ?editors=1010
I think the error message would be slightly improved by:
“Unable to find a controller when searching for the “result” outlet of the “search” controller. Ensure CSS selector “#result” is correct and target element has a data-controller attribute that includes “result”, e.g. “data-controller=result”.”
(Not sure how easy including the CSS selector woud be, it’s not particularily important)
Speaking to my original point: if you’re planning a release soon-ish without #648 I would suggest adding another senctence to the error message with an explicit reference to this issue , i.e. “If the target is correctly declared as the “result” controller, it might not be connected yet. See https://github.com/hotwired/stimulus/issues/618 for a workaround.”
Same thing is happening to us in the
connectwe have also encountered similar issue indisconnectwhen using Turbo Drive. Outlet can be disconnected before the controller which causes same error as above.has[identifier]Outletalso returnstrueeven though outlet isn’t there which makes it harder to check for the situation.[identifier]Outlets.lengthcan be used as a workaround even though it shows warning with the same text as an error in the javascript console (at least in safari).setTimeoutworkaround unfortunately didn’t work in our case when lazily loading several controllers via importmaps from individual javascript files. It can be hard to set delay properly if we don’t know when the outlet controller will be loaded.Hey @rgarner, #648 fixed the underlying issue. If you want to give it a shot feel free to pull in the dev-build to double check if it also solves your use-case.
By wrapping codes in a setTimeout avoid this warning for me:
@drjayvee I hope you meant to say
iihere 🙈 But I would agree with you in general.In any case, I started to explore
iiin #648, which looks promising so far.@promisedlandt I’m open to update the error message if you have an idea on how to improve it.
Luckily, the root cause is fixed and already implemented with #648, but I haven’t had the time to finish it up yet. I’m looking to finish that up soon!
The problem is the order how Stimulus connects controllers. So this issue is dependent how the elements are found in the DOM and in which order the MutationObserver processes them.
So if you try to access the outlets in the
connect()action of the “host controller” and the dependent outlets haven’t connected yet you will get theThe provided outlet element is missing the outlet controller "result" for "search"error.This won’t work:
However, this works as intended.
But in both scenarios you can access the outlets after the dependent outlet controllers have connected. Also the outlet callbacks work as expected.
There are few options how we can solve that you can access outlets in any case, independent of DOM order appearance:
data-controllerattribute attached, build a “dependency tree”, sort the elements to be connected accordingly and then connect them in that order. This works if we disallow circular outlet dependencies.data-controllerattribute. If it does, we could connect that controller on-the-fly and enforce it that way. However, there is the downside that it might try to connect that controller again at a later point, because it was scheduled to be connected. But maybe we can also enhance the “connect logic” in that step to make sure there isn’t a connected controller already and just disregard the second connect attempt.connect(), but this feels like a weird limitation.this.[name]Outletandthis.[name]Outletsasync and make them return a promise which resolves as soon as the dependent controllers are connected. But this would probably lead to a breaking change.I’ll investigate which option is the best solution here. Personally my vote would go for option ii. But happy to hear what others think.