Livewire Emit Event Listeners from Child to Parent

I have a button...

<button wire:click="$emitTo('components.modals.player-modal', 'showModal', '{{ $player->id }}')" class="focus:outline-none"> {{ $player->first_name . ' ' . $player->last_name }} </button>

...That triggers a modal similar to the Code Course tutorial: Global Modals with Livewire and Alpine.js. Where a "PlayerModal" livewire controller extends a "Modal" livewire controller...

namespace App\Http\Livewire\Components\Modals;

use Livewire\Component;
use App\Models\Player;

class Modal extends Component
{
    public $showModal = false;
    public Player $player;

	public $listeners = [
		'showModal' => 'showModal',
	];

	public function showModal(Player $player)
	{
		$this->showModal = true;
		$this->player = $player;
	}
}
namespace App\Http\Livewire\Components\Modals;

use Livewire\Component;
use App\Http\Livewire\Components\Modals\Modal;
use App\Models\Player;

class PlayerModal extends Modal
{
    public $playerModalShow = 'playerEdit'; // default view
	public Player $player;

	public $listeners = [
		'playerModalNavClicked' => 'changePlayerView'
	];

	public function mount(Player $player)
    {
        $this->player = $player;
    }

    public function changePlayerView($value)
    {
        $this->playerModalShow = $value;
    }

    public function render()
    {
        return view('livewire.components.modals.player-modal');
    }
}

...In the blade of the PlayerModal, I have 3 child livewire components:

  • PlayerModalNav (navigation buttons)
  • PlayerModalEdit and PlayerModalRatings (2 sections underneath the nav I want to show/hide based on the listener event: playerModalNavClicked which is changed by the PlayerModalNav buttons.
<div class="grid sm:grid-cols-1 md:grid-cols-4 gap-3 mt-6">
    <button wire:click="changePlayerView('playerEdit')" class="text-white bg-accent hover:scale-105 hover:brightness-150 shadow-lg font-medium rounded-lg text-lg px-5 py-2.5 text-center mb-2 transition duration-300 {{ $playerModalShow == 'playerEdit' ? 'active-nav' : '' }}">Edit Player</button>
    <button wire:click="changePlayerView('playerActions')" class="text-white bg-accent hover:scale-105 hover:brightness-150 shadow-lg font-medium rounded-lg text-lg px-5 py-2.5 text-center mb-2 transition duration-300 {{ $playerModalShow == 'playerActions' ? 'active-nav' : '' }}">Actions</button>
	<button wire:click="changePlayerView('playerStats')" class="text-white bg-accent hover:scale-105 hover:brightness-150 shadow-lg font-medium rounded-lg text-lg px-5 py-2.5 text-center mb-2 transition duration-300 {{ $playerModalShow == 'playerStats' ? 'active-nav' : '' }}">Stats & Contracts</button>
	<button wire:click="changePlayerView('playerRatings')" class="text-white bg-accent hover:scale-105 hover:brightness-150 shadow-lg font-medium rounded-lg text-lg px-5 py-2.5 text-center mb-2 transition duration-300 {{ $playerModalShow == 'playerRatings' ? 'active-nav' : '' }}">Ratings</button>
</div>
namespace App\Http\Livewire\Components\Modals;

use Livewire\Component;
use App\Models\Player;

class PlayerModalNav extends Component
{
	public $playerModalShow;

    public function changePlayerView($value)
    {
        $this->playerModalShow = $value;
        $this->emit('playerModalNavClicked', $value);
    }

    public function render()
    {
        return view('livewire.components.modals.player-modal-nav');
    }
}

My issue now is when I add the $listeners on the Player Modal livewire controller (PlayerModal.php), the button that triggers the modal stops working.

Any assistance would be greatly appreciated!

alexjolley
alexjolley
0
11
1253
Haz
Haz
Moderator

Hello,

It looks like your emitTo is wrong.

Quote from the Livewire documentation:

Sometimes you may only want to emit an event to other components of the same type.

In these cases, you can use emitTo:

https://laravel-livewire.com/docs/2.x/events#scope-by-name

It doesn't look like playerModalNavClicked is a component. Try player-modal (PlayerModal) instead.

I have 3 child livewire components

Make sure they are correctly nested.

You could also just use emit instead of emitTo just to get it working initially, and then refactor.

alexjolley
alexjolley

Ahh yes, I had "emit" only initially and was trying some different things--forgot to change it back. But still no success. When the $listeners are added on the PlayerModal.php, the button click no longer opens the modal.

What do you mean by correctly nesting the components?

Haz
Haz
Moderator
the button click no longer opens the modal

Are you getting any errors?

What do you mean by correctly nesting the components?

I mean, double check they are nested in the component view. but I guess this doesn't matter if you are using emit.

Can you share more code? Could you share all of the components and views perhaps?

alexjolley
alexjolley

No errors...just pointer cursor with no action on click.

And yes, happy to share everything. I should also mention, I have this exact navigation system working in another part of the app. I have everything exactly like I have it there but that modal button just stops working when the $listeners are added on PlayerModal.php.

<div>
	<x-modals.modal-base wire:model="showModal">
		<div class="relative w-full max-h-full shadow-md p-4 bg-light0 dark:bg-dark0 text-light5 dark:text-dark5">
			<div>

	            <!-- Modal content -->
	            <div class="px-6 pb-6">
                    <!-- CONTENT HERE -->

	            	<!-- MODAL NAV -->
	            	@livewire('components.modals.player-modal-nav', ['playerModalShow' => $playerModalShow])
	            </div>
	            <!-- Show content based on nav -->
	            <div>
	            	@if ($playerModalShow == 'playerEdit')
	            		@livewire('components.modals.player-edit')
	            	@elseif ($playerModalShow == 'playerRatings')
	            		@livewire('components.modals.player-ratings')
	            	@endif
	            </div>
	        </div>
		</div>
	</x-modals.modal-base>
</div>
<div
	x-data="{showModal: @entangle($attributes->wire('model')).defer}"
	x-show="showModal"
	x-on:keydown.escape.window="showModal = false"
	class="fixed top-0 left-0 right-0 z-50 p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full"
>
	<div x-show="showModal" class="fixed inset-0 transform" x-on:click="showModal = false">
		<div class="absolute inset-0 bg-light5 opacity-80"></div>
	</div>

	<div x-show="showModal" class="top-10 border border-4 dark:border-dark2 shadow-2xl relative w-full max-w-4xl max-h-full rounded-lg overflow-hidden transform w-full mx-auto">
		{{ $slot }}
	</div>
</div>
<button wire:click="$emitTo('components.modals.player-modal', 'showModal', '{{ $player->id }}')" class="focus:outline-none">
	{{ $player->first_name . ' ' . $player->last_name }}
</button>

I believe that's everything relevant to the modal and nav.

Haz
Haz
Moderator

Hi,

<button wire:click="$emitTo('components.modals.player-modal', 'showModal', '{{ $player->id }}')" class="focus:outline-none">
	{{ $player->first_name . ' ' . $player->last_name }}
</button>

Where is this exactly? The issue you are describing sounds very odd. Mind sharing the repo so I can take a closer look for you?

I think if I was to try to re-create the issue with the provided code, I don't think I would encounter the issue, but I am sure there are many other parts that can factor to the behaviour you are seeing.

alexjolley
alexjolley

That button is on "franchise-rosters.blade.php" page.

I'm having trouble getting the repository on github. So I've just zipped the directories and put them in a Google Drive link. Hope that's ok.

https://drive.google.com/file/d/1A7uIMR8QGO9Av_oDHZq9EBuAlmH9S2AH/view?usp=sharing

I appreciate your help on this.

Haz
Haz
Moderator

Yes, that's fine. Looking into it now. Will update this post soon.

@alexjolley Going to need some information from you. How can I clearly reproduce the issue?

I have imported the default_values.sql into the database. I have tried creating a franchise, but it simply redirects back and fails to create the resource. Even after manually creating the records in the database, I am still not seeing a simple path to the issue.

I am also getting some security warnings when running npm run dev. What's with that?

Image

alexjolley
alexjolley

Ahh yes, importing those default_values.sql should get the DB to a working point. Then the team rosters need to be imported to the DB by going to the "Import Roster" page in the app, naming it and clicking "Import Existing JSON File." This will import a json file from inside storage/madden_data/maddenexporter_json/rosters to the DB. Then you can return to the "Home" page and click the "New Franchise" button. You can select any roster type and team and it will generate a new franchise. On the "rosters" tab, the goal is to click on a player's name and the modal will open to show more details about the player. This is where the problem occurs and when the $listeners are added on the PlayerModal.php that button on the player name stops opening the modal.

Not sure what that security warning is about...I'm not getting that. Maybe once the roster file is imported that will be fixed?

Thanks

Haz
Haz
Moderator
Solution

@alexjolley It seems you are overriding the listeners defined in Modal.php.

Removing this from PlayerModal.php:

	public $listeners = [
		'playerModalNavClicked' => 'changePlayerView'
	];

Adding this to Modal.php:

class Modal extends Component
{
    public $showModal = false;
    public Player $player;

	public $listeners = [
		'showModal' => 'showModal',
	];

	public function showModal(Player $player)
	{
        dd('hello');
		$this->showModal = true;
		$this->player = $player;
	}
}

I see the dd after clicking the button. The modal doesn't appear to show, but that's another issue entirely. I'll leave you to debug that.

Image

If you want to keep the original listener, make sure to add showModal there too.

	public $listeners = [
        'showModal',
		'playerModalNavClicked' => 'changePlayerView',
	];

You are also defining $player twice too.


Edit: If you move the Livewire component to the top, the modal shows, though the styling is a bit borked.

Move from:

    </div>

    @livewire('components.modals.player-modal')
</div>

Move to:

<div>
    @livewire('components.modals.player-modal')
    <!-- HEADER -->

Image

I hope this helps! :)

alexjolley
alexjolley

You're a life-saver, thank you!! I should've realized that but I really appreciate a second pair of eyes.

Haz
Haz
Moderator

@alexjolley No worries. Don’t forget to mark a best answer if solved. :)