In laravel 10/livewire 3 app having function declared as I have a component for user's actions and I need when user clicked some action to change status of the reaction at once. I have a code:
class UserReaction extends Component
{
use AppCommonTrait;
public int $id;
public string $dataType;
#[Reactive]
public array $reactionActions = [];
public function mount(int $id, string $dataType)
{
...
$this->reactionActions = []; // collect all data in 1 array
foreach ($reactionActions as $key => $label) {
$voted = in_array($key, $userReactions);
$reactionCount = 0;
foreach ($totalUserReactions as $totalUserReaction) {
if ($totalUserReaction['action'] === $key) {
$reactionCount = $totalUserReaction['reaction_count'];
}
}
$this->reactionActions[$key] = [
'key' => $key,
'label' => Str::lower($label),
'voted' => $voted, // if logged user has voted this action
'reactionCount' => $reactionCount, // all users has voted this action
];
}
}
public function render()
{ // all data passed as parameter
return view('livewire.user-reaction', ['reactionActions' => $this->reactionActions]);
}
public function voteReaction($voted, $action)
{
$modelType = $this->getModelType($this->dataType);
if ( ! $voted) { // to add reaction
$reaction = Reaction::getByModelType($modelType)->getByModelId($this->id)->getByAction($action)->getByUserId(auth()->user()->id)->first();
if (empty($reaction)) { // not to vote for the second time
Reaction::create([
'model_id' => $this->id,
'model_type' => $modelType,
'user_id' => auth()->user()->id,
'action' => $action
]);
foreach ($this->reactionActions as $key => $reactionAction) {
if($reactionAction['key'] === $action) {
$this->reactionActions[$key]['voted'] = true;
// TRY TO CHANGES VOTED STATUS OF AN ACTION - BUT I DO NOT SEE THE CHANGES IN BLADE FILE
}
}
}
} else { // to cancel reaction
$reaction = Reaction::getByModelType($modelType)->getByModelId($this->id)->getByAction($action)->getByUserId(auth()->user()->id)->first();
if ( ! empty($reaction)) {
$reaction->delete();
}
}
}
and in blade file :
<div class="flex gap-2">
$reactionActions::{{ print_r($reactionActions, true) }}
// I expected changed would be applied in an array above - but not
<hr>
<hr>
<hr>
@foreach($reactionActions as $index => $reactionAction)
<button
class="py-1.5 px-3 hover:text-green-600 hover:scale-105 hover:shadow text-center border rounded-md border-gray-400 h-8 text-sm flex items-center gap-1 lg:gap-2 news-user-reactions-item{{$reactionAction['voted'] ? '-voted' : ''}}">
<img src="img/emoji/{{$reactionAction['label']}}.png" class="w-full" alt="{{ $reactionAction['label'] }}"
title="{{$reactionAction['voted'] ? 'You have already voted "' . $reactionAction['label'] . '" reaction. You can cancel your vote' : 'You can vote "' . $reactionAction['label'] . '" reaction'}}" wire:click="voteReaction( '{{ $reactionAction['voted'] }}', '{{ $reactionAction['key'] }}' )" />
<span>{{ $reactionAction['key'] }}:{{ $reactionAction['reactionCount'] }}</span>
</button>
@endforeach
</div>
I declared #[Reactive] for $reactionActions var - buit it did not help.
Which way is correct ?
#[Reactive] is used where a parent component wants to update a child component when a passed property is changed: this is not the case, so it is not needed.
The problem is caused by the interaction between wire:click action:
wire:click="voteReaction( '{{ $reactionAction['voted'] }}', '{{ $reactionAction['key'] }}' )
and the the test performed in the voteReaction method:
if($reactionAction['key'] === $action) {
since $reactionAction['key'] contains a number and voteReaction() receives a string and you are applyng a strict type comparison with ===
, the test fails.
Solution: apply a loose comparison using the ==
operator or pass the parameter without quotes.
Note that also the first parameter, when the key 'voted' contains the false value, is printed as an empty string: true and false are printed as 1 and '' from the blade {{ }} operator, this can work but only with a loose comparison. A similar option can be casting the value to int:
wire:click="voteReaction( {{ (int)$reactionAction['voted'] }}, {{ $reactionAction['key'] }})
A more important observation: since in the mount() you are keying the array with the value of $key, the $action parameter corresponds to the array position, so the foreach:
foreach ($this->reactionActions as $key => $reactionAction) {
.....
}
can be substituted by a simple:
$this->reactionActions[$action]['voted'] = true;