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>