Hotwire Discussion

Persist stimulus controller when moving element

Is there a way to persist the stimulus controller while moving an element on a page?

I have hundreds of rows in a table, and each row has a few stimulus controllers with some complex connection logic. I want to add column sorting to this table, where clicking a column header changes the order. To do this I call appendChild to move the children around. This causes hundreds of controllers to reconnect even though they don’t do anything new.

Here’s a codepen example: https://codepen.io/rbates/full/YzNwwOO

The performance would be much better if there was a way to persist the stimulus controller while the element moves. Is this possible?

It would be great if the connection was lazy, like the old jQuery on method.

Walter

Consider using initialize() instead of connect() for setup logic since it’s only called once.

Example (forked from yours): https://codepen.io/javan/pen/yLgeoBd

Docs: Stimulus Reference

Methods

Method Invoked by Stimulus…
initialize() Once, when the controller is first instantiated
connect() Anytime the controller is connected to the DOM
disconnect() Anytime the controller is disconnected from the DOM

Reconnection

A disconnected controller may become connected again at a later time.

When this happens, such as after removing the controller’s element from the document and then re-attaching it, Stimulus will reuse the element’s previous controller instance, calling its connect() method multiple times.

1 Like

Unfortunately initialize won’t work for me since I need access to the DOM element to set things up.

I understand it’s reconnecting because the element is being removed and then added, however I wish there was a way to tell Stimulus to persist the connection since the DOM element isn’t changing, just its location.

There are a couple work arounds I’ve found.

  1. Add a boolean value to skip connection setup after it’s already connected. This means it still reconnects the controller which is a little overhead.
  2. Use flexbox and order to sort rows. This isn’t compatible with tables, but it persists the elements so its very fast.

They have their drawbacks but I think it’s acceptable for my cases.

There shouldn’t be any difference between initialize() and connect() in that regard. Stimulus literally just calls both of those methods the first time a controller element appears in the DOM. You can think of initialize() as “first connect” if that helps.

1 Like

Thank you! I misunderstood that initialize happened on connection. I believe this will solve this issue for me.

I also misunderstood until reading this thread. I suggested an update to the documentation here:

One seemingly little-known detail about the Stimulus life-cycle is that it actually goes:

  1. initialize
  2. valueChanged callbacks
  3. connect

If you have a value defined on your controller, its “Changed” callback fires after initialize but before connect, presumably as the value transitions from undefined to whatever it starts as.

I’ve occasionally used this functionality to solve thorny life-cycle issues.

1 Like

@leastbad - I’m interested… can you provide some examples?

Sure, check out stimulus-hotkeys.

In this particular example, is this so you can dynamically change the hotkeys?

In other words, for this controller why wasn’t connect/disconnect good enough?

Yes, you can use CableReady’s set_dataset_property operation to update the mapping object dynamically. As the README explains, it means you can provide users with a UI to set up their own custom mappings for your app, and update them in real-time thanks to StimulusReflex Nothing Morphs and CableReady.

It’s been a while since I built this, man - I suggest that you try out the code locally and experiment with the console log to satisfy your curiousity.

My quick read of the code is that the 3rd party library did not want to be unbinded if it’s not already set up, so I could use bindingsValueChanged to establish an initial value that would be ready when connect ran. And later, if the value does change, all mappings are unbound before the mappings are updated and new bindings are created. Still, memory is fuzzy and that could be only part of the story. The important part is that having an event fire in between initialize and connect is something that might allow you to design your solution far more efficiently than others might realize is possible - and that is a productivity arbitrage for you.

Seriously, though: me explaining it is like, 1/5th as good as actually trying it out yourself. As you build more and more Stimulus controllers, this sort of thing comes up just often enough to be annoying and having this technique in your pocket is like knowing how to use a flint and steel if you’re stranded in the woods.

@leastbad - great tip, thanks.

1 Like

@tleish The valueChanged callbacks can be super helpful. I’ve been using them to have controllers speak to other controllers. Inside of the first controller I’ll change a data value on a second element so that the second controller will have its valueChanged listener triggered and its just gotten data passed to it through data value.

Wouldn’t it be easier to use events, in this case?