Skip to content
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

Ensure Scope is connected before accessing outlets #648

Conversation

marcoroth
Copy link
Member

@marcoroth marcoroth commented Jan 28, 2023

This pull request ensures that the associated scope of an outlet controller is connected before evaluating and returning the outlet controllers.

The outlet getter returned nothing/raised an error when it was accessed within the connect() function of the host controller and if the controller elements were placed in a certain order in the DOM.

The example below demonstrates this issue. If one tried to access the this.resultOutlets in the connect() action of the search host controller it wouldn't return them. The elements with the result controllers weren't initialized/connected yet because they appear after the element with the search controller in the DOM.

<div data-controller="search" data-search-result-outlet=".result">Search</div>
<div data-controller="result" class="result">Result</div>
<div data-controller="result" class="result">Result</div>

Previously the connect order for this example looked like:

START:  CONNECT search
FINISH: CONNECT search
START:  CONNECT result
FINISH: CONNECT result
START:  CONNECT result
FINISH: CONNECT result

With this pull request the order looks like this:

START:  CONNECT search
START:  CONNECT result
FINISH: CONNECT result
START:  CONNECT result
FINISH: CONNECT result
FINISH: CONNECT search

Which allows that outlets can now be accessed in the connect() action of the host controller independent of in which order they might appear in the DOM.

You can test this pull request with the following dev-build:

yarn add @hotwired/stimulus@https://github.com/hotwired/dev-builds/archive/@hotwired/stimulus/20a3989.tar.gz

Resolves #618

@seanpdoyle
Copy link
Contributor

Would it be possible to include a test scenario that exercises this behavior?

@marcoroth
Copy link
Member Author

yeah, I'm working on that, that's also why the PR is a draft 🙈

I just wanted to get this posted so that people can see the progress on it while being as transparent as possible about it

@jorismak
Copy link

Still wondering what this would do with lazy controllers ? Just delay the finish of the initial controller a lot ?

@marcoroth
Copy link
Member Author

@jorismak I would imagine that the order how they are feteched is different.

It would just pause the connect() of the initial controller at the point where you try to access the outlets when the outlet controller wasn't connected already.

But I don't see how it would have a negative impact or a different behavior compared to how it worked before.

Otherwise it would be more than appreciated if you could report back how the dev-build works out for you! 🙏🏼

@marcoroth marcoroth marked this pull request as ready for review June 18, 2023 21:27
@marcoroth marcoroth force-pushed the ensure-scope-is-connected-before-accessing-outlets branch from b94a694 to 15a3675 Compare June 18, 2023 21:34
@marcoroth
Copy link
Member Author

The good news is that this is also working with lazy-loading. There isn't really a way we can test the lazy-loading behavior when controllers are referencing unloaded/unregistered outlet controllers in connect() though.

Since the (lazy)-loading of Stimulus controllers via Importmaps can't be controlled and is up to the device/browser/network connection it's possible that there might be some cases where the controllers aren't loading in time and it would still reference an empty outlets array when trying to access this.[outletName]Outlets in connect().

There are a few ways to avoid this from happening:

  • only reference controllers which were defined earlier in the document, Stimulus processes the controllers top to bottom.
  • eager-load the referenced outlet controller/s, ideally using:
    # config/importmap.rb
    pin_all_from "app/javascript/controllers", under: "controllers", preload: true
  • don't access this.[outletName]Outlets in connect(), instead move the access to a non-connect method

@marcoroth marcoroth merged commit eda0f9d into hotwired:main Jun 19, 2023
1 check passed
@marcoroth marcoroth deleted the ensure-scope-is-connected-before-accessing-outlets branch June 19, 2023 10:27
@zachfeldman
Copy link

This is awesome @marcoroth . Will there be a new version cut soon that includes this? I think the last release was in November of last year if package.json is right? 8ab6e59

@marcoroth
Copy link
Member Author

Hey @zachfeldman, yeah that's right. We are working to get a new release out soon. There are still some open things we want to get resolved first though.

If you need this fix right now, you can use the latest dev-builds with:

yarn add @hotwired/stimulus@https://github.com/hotwired/dev-builds/archive/refs/tags/@hotwired/stimulus/2a3603c.tar.gz

I hope this helps. Thanks for your understanding! 🙏🏼

@zachfeldman
Copy link

@marcoroth awesome thanks so much for the update and instructions on how to run the bleeding edge!

We're using the built in function to fire when outlets are run as a workaround for now but are looking forward to a release landing with this feature soon.

Thanks for all that you do for Stimulus!!

@tonywok
Copy link

tonywok commented Aug 14, 2023

Hmm, I still seem to seeing this issue (stimulus-rails 1.2.2 which appears to have this fix from https://github.com/hotwired/stimulus/releases/tag/v3.2.2) when referencing outlets from the parent's connect some of the time. I'll try to put together a minimal example and confirm it's not due to something else I'm doing 🤔

@marcoroth
Copy link
Member Author

Yeah, I think there still might be some edge cases where we cannot 100% guarantee that your code is running in the right order when you are using import-maps, especially combined with lazy-loading, just because of the nature of the technology.

@tonywok
Copy link

tonywok commented Aug 14, 2023

fwiw I'm using import maps and the particular example isn't lazy loaded.

I'm confused as to why the callback fires if the controllers aren't yet instantiated. Is there a lower level function I could be using manually as a work around? I'm trying to grok it now. Will update if I find anything. 😅 Appreciate all your efforts, btw.

@marcoroth
Copy link
Member Author

Which callbacks are you referring to? The [name]OutletConnected? They should work reliability, since they are also async.

The best way to make sure all outlet controllers are available is to not access the outlets in connect(). But I'm not sure if that's what you were asking.

But I'm afraid that it would be best if you could provide us with a minimal reproduction setup to be certain.

@coorasse
Copy link

coorasse commented Aug 29, 2023

@marcoroth I just faced the same issue. I am accessing an outlet in the connect() and although I check if the controller is present, it is still not.
CleanShot 2023-08-29 at 11 11 53@2x

CleanShot 2023-08-29 at 11 11 42@2x

I am happy to debug this together in the next days, just reach me out via slack.

I think it makes sense...I also understand why this happens. I guess I have to rethink the logic behind my controller. It makes completely sense that within the connect(), the outlets might be available or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Outlets not available in connect/disconnect
6 participants