javascriptalpine.js

Access child data from parent with AlpineJS


AlpineJS' x-data can be nested, so one can access parent data inside a child.

But can one access child data from its parent?

Contrived example:

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>

<div x-data="{ get nAvailable() { return $el.querySelectorAll('.product.avail').length },
               get nSoldOut()   { return $el.querySelectorAll('.product:not(.avail)').length },
             }">

  <div x-data="{ avail: true }"  class="product" x-bind:class="avail && 'avail'">Product 1</div>
  <div x-data="{ avail: true }"  class="product" x-bind:class="avail && 'avail'">Product 2</div>
  <div x-data="{ avail: true }"  class="product" x-bind:class="avail && 'avail'">Product 3</div>
  <div x-data="{ avail: true }"  class="product" x-bind:class="avail && 'avail'">Product 4</div>
  <div x-data="{ avail: false }" class="product" x-bind:class="avail && 'avail'">Product 5 <span x-text="avail || '(sold out)'"></span></div>

  <br/>
  <div>Available: <span x-text="nAvailable"></span></div>
  <div>Sold out: <span x-text="nSoldOut"></span></div>
</div>

That works.

But child state is converted to a class (.avail) that the parent can detect by scanning the DOM. In a large page that is slow.

Is it possible to do this without that class?


Solution

  • Data typically flows downward from parent to child components, following a unidirectional data flow pattern. This "props down, events up" approach means that while parents can easily pass data to their children, accessing child state from a parent component usually requires additional mechanisms such as event emission, callbacks, or state lifting.

    So, instead of trying to send state up, a better approach would be to maintain the state in the parent so that all child components can update:

    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
    
    <div x-data="{ 
      products: [
        { id: 1, name: 'Product 1', avail: true },
        { id: 2, name: 'Product 2', avail: true },
        { id: 3, name: 'Product 3', avail: true },
        { id: 4, name: 'Product 4', avail: true },
        { id: 5, name: 'Product 5', avail: false }
      ],
      get nAvailable() { 
        return this.products.filter(p => p.avail).length 
      },
      get nSoldOut() { 
        return this.products.filter(p => !p.avail).length 
      }
    }">
      <template x-for="product in products" :key="product.id">
        <div class="product" x-bind:class="product.avail && 'avail'">
          <span x-text="product.name"></span>
          <span x-show="!product.avail"> (sold out)</span>
          <!-- toggle btn -->
          <button x-on:click="product.avail = !product.avail">Toggle Availability</button>
        </div>
      </template>
    
      <br/>
      <div>Available: <span x-text="nAvailable"></span></div>
      <div>Sold out: <span x-text="nSoldOut"></span></div>
    </div>

    Here i'm using many alpine features so if in doubt please reefer to the docs