alpinejs / alpine

A rugged, minimal framework for composing JavaScript behavior in your markup.
https://alpinejs.dev
MIT License
28.29k stars 1.23k forks source link

feat: Update lifecycle and mutation #4000

Closed Matsa59 closed 9 months ago

Matsa59 commented 9 months ago

The goal is to support "add reorder" that happen in the same mutation.

Original PR: https://github.com/alpinejs/alpine/pull/3951 (that was revert because it introduced a bug)

Matsa59 commented 9 months ago

There is something weird, after my PR was merged (the original one), the code was rewrite for something incorrect https://github.com/alpinejs/alpine/commit/3196c6ac667a040d0c5541644f2a389325b5c356

The PR was merged originally with this state https://github.com/alpinejs/alpine/commit/c68aba00001d2a5df8c3b632a79ac0bf90fcedc0

That's why everything was broken (cf https://github.com/alpinejs/alpine/pull/3995).

I guess it's a merge error

calebporzio commented 9 months ago

Did my move to Sets not fix this?

Matsa59 commented 9 months ago

Did my move to Sets not fix this?

The first try was to update the mutation system. Then the code has been changed and to update the lifecycle system.

You've updated an old piece of code and I guess you force push it or something like that (so it eras the code ghat was merged)

ekwoka commented 9 months ago

@calebporzio The sets would only fix an issue of Alpine trying to initialize the element twice.

The concern in the original PR was that the element had both an add and a remove, but had never actually been initialized.

Alpine treated it as a "move" and not an "add", and thus just ignored it.

ekwoka commented 9 months ago

@Matsa59 Can you add a test to verify this works correctly?

Ideally first make it not work (without your changes) and then validate your changes against it.

calebporzio commented 9 months ago

Ah, I see. Thanks for the clarification @ekwoka. I'm slightly hesitant to pull this in for some reason - maybe I'd prefer more of that code to live in the mutation handler? Idk. It feels simple right now, and adding this check for an inited property doesn't feel right. Any opinions here? Any other ways to tackle this that we're not thinking of?

Also, yes tests would be great. Thanks.

Matsa59 commented 9 months ago

@ekwoka test has been added, I used the same code as does not initialise components twice when contained in multiple mutations but I add/remove/add. This test failed on alpinejs main but works on my branch (as you asked to do)

Matsa59 commented 9 months ago

Plus updating the lifecycle system may increase Alpine speed.

Currently (on main) the check if its a "move" operation, iterate on every deleted nodes for every inserted nodes. This could have a significant impact on performance.

ekwoka commented 9 months ago

Well, with the other recent change of using Set it should be much better already (but only guaranteed to be sublinear).

But yes, this should be a bit quicker.

calebporzio commented 9 months ago

Thanks @Matsa59 - in the future please checkout a non-main branch to PR back to this repo. Otherwise I can't commit to your branch.

I opened a new branch here: https://github.com/alpinejs/alpine/pull/4012

I'm going to merge it with a few tweaks after tests pass. Thanks!

Matsa59 commented 9 months ago

Ok sure, I'll do another branch next time.

However on your tweaks you have some mistakes. You didn't rename every "_x_isInit"

@calebporzio https://github.com/alpinejs/alpine/commit/2f59ee2bc115b32bee8cdd67c6edd6f31d01214c#r138214282

calebporzio commented 9 months ago

Haha good catch - yeah I see the failure and am working on it!

joshhanley commented 8 months ago

@PhiloNL is experiencing issues due to this PR - see this issue on his repo https://github.com/wire-elements/modal/issues/423

Waiting to see if we can reproduce it in a smaller testcase.

PhiloNL commented 8 months ago

It seems Alpine is no longer picking up any changes that happen after the initial render (using Livewire v2, in this example).

For example:

<?php

class Component extends Component
{
  public $ready = false;
}
<div x-data="{
        select() {
            console.log(this.$refs.resultGroups);
        }
    }" 

    wire:init="$set('ready', true)">

      @if($ready)
      <div x-ref="resultGroups">
          <div class="wep-spotlight-group">
              <ul x-ref="results">
                  <li @click="select" x-data>
                     Contacts
                  </li>
                  <li @click="select" x-data>
                     Billing
                  </li>
                  <li @click="select" x-data>
                     Docs
                  </li>
              </ul>
          </div>
      </div>
      @endif

</div>

Click any of the <li> and it will log undefined. I also had to add x-data to every <li> for the @click directive to work (which was not needed previously because it's a child element within an existing Alpine instance).

If you set ready to true by default, all works as expected. Once you set it to false and defer the loading of the <li> items it breaks.

Matsa59 commented 7 months ago

If you add x-data on the <div x-ref="resultGroups"> it should work.

@calebporzio I'm not sure reverting a PR that fixed another bug is a nice patch


edit: hum I guess the problem comes from x-ref that is not "refresh" when the dom is updated. Maybe we could find something on the x-ref init system.

Technically, the x-data form the parent is been init, so IMO it's not the problem of AlpineJS. Or the x-refs system should be calculate on exec and not on init

A fix that should work:

<div x-data="{
        resultGroups: nil,
        select() {
            console.log(this.resultGroups);
        }
    }" 

    wire:init="$set('ready', true)">

      @if($ready)
      <div x-data x-init="resultGroups = $el">
          <div class="wep-spotlight-group">
              <ul x-ref="results">
                  <li @click="select" x-data>
                     Contacts
                  </li>
                  <li @click="select" x-data>
                     Billing
                  </li>
                  <li @click="select" x-data>
                     Docs
                  </li>
              </ul>
          </div>
      </div>
      @endif

</div>

you could also use x-if or x-show and bind the var $ready to the js state

(or something like that, you get the idea, i didn't test it)

PhiloNL commented 7 months ago

Thanks! But that would still be a breaking change for every Livewire v2 app out there that uses Alpine v3. So it would likely affect thousands of users. It would be great if no changes were required by the end-user.