Skip to content
Advertisement

How to access a property or a method of alpine.js parent component from a child component?

This is kind of an example scenario what the problem looks like,

<div x-data="{ count : 0 }">
    <div x-data>
        <span x-text="count"></span>
        <button x-on:click="count++">Increment</button>
        <button x-on:click="count--">Decrement</button>         
    </div>
</div>

It would be able to increase/decrease the data count from the child component. I thought of handling it through dispatching custom events using $dispatch() but then again in terms of design, I might need to write listeners on both parent and child component which make the logic more complex since it should be reactive as well.

There was a Github issue, and none of the proposed solutions was working.

Advertisement

Answer

I thought of handling it through dispatching custom events using $dispatch() but then again in terms of design, I might need to write listeners on both parent and child component which make the logic more complex since it should be reactive as well.

This is the crux of the issue, in order to do parent-child and child-parent communication you’ll need to use events. In the case of child -> parent, you’ll trigger increment and decrement events (which will be listened to in the parent component using x-on:increment and x-on:decrement). In the case of parent -> child, you’ll need to use $watch to trigger updates whenever count updates (I’ll used the new-count event name), this will be listened to on the window from the child component using x-on:new-count.window.

Here’s the full working solution (see it as a CodePen):

<div
  x-data="{ count : 0 }"
  x-init="$watch('count', val => $dispatch('new-count', val))"
  x-on:increment="count++"
  x-on:decrement="count--"
>
  <div>In root component: <span x-text="count"></span></div>
  <div
    x-data="{ count: 0 }"
    x-on:new-count.window="count = $event.detail"
  >
    <div>In nested component <span x-text="count"></span></div>
    <button x-on:click="$dispatch('increment')">Increment</button>
    <button x-on:click="$dispatch('decrement')">Decrement</button>
  </div>
</div>

In the case you’ve presented, the count state might be better served by using a global store that integrates with Alpine.js such as Spruce, in which case we’ll read and update a shared global store to which both the parent and child components are subscribed (see the Spruce docs). You can find the working example in the following CodePen.

<div x-data x-subscribe="count">
  <div>In root component: <span x-text="$store.count"></span></div>
  <div x-data x-subscribe="count">
    <div>In nested component <span x-text="$store.count"></span></div>
    <button x-on:click="$store.count ++">Increment</button>
    <button x-on:click="$store.count--">Decrement</button>
  </div>
</div>
<script>
  Spruce.store('count', 0);
</script>

The final solution that should be mentioned is that removing the nested component would mean that the count increment and decrement would work as expected. Obviously this example was probably simplified & meant to be illustrative, so this solution might not work in a lot of cases. Note: the only difference is removing the second x-data.

<div x-data="{ count : 0 }">
    <div>
        <span x-text="count"></span>
        <button x-on:click="count++">Increment</button>
        <button x-on:click="count--">Decrement</button>         
    </div>
</div>
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement