Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ignaMani's avatar

Error 404 on parent when removing child in a loop using dispatch

Hello, i'm having a problem when deleting a child in a loop, I throw the dispatch to the parent to delete the item, but after deleting it, throws 404.

This is the [show-team.blade.php] which is the team card (the child component)

<?php

use function Livewire\Volt\{state, mount, computed, emit, refresh, on, uses};
use App\Actions\Jetstream\AddTeamMember;
use App\Events\TeamDeleted;
use App\Events\TeamMemberDeleted;
use App\Models\Brief;
use App\Models\User;
use App\Models\Team;
use Mary\Traits\Toast;

uses([Toast::class]);

state(['brief', 'isAdmin', 'crud', 'briefTimeHasFinished', 'editModal' => false]);

state(['team'])->reactive();

$sortedTeamMembers = computed(function () {
    $teamMembers = $this->team->users()->get();
    $teamMembers = $teamMembers->sortBy(function ($member) {
        if ($member->pivot->role === 'owner') {
            return [0, $member->first_name];
        } elseif ($member->pivot->role === 'participant') {
            return [1, $member->first_name];
        } else {
            return [2, $member->first_name];
        }
    });
    return $teamMembers;
});

$addTeamMember = function () {
    if (!$this->isOnAnyBriefTeam) {
        $this->team->users()->attach(auth()->user()->id, ['role' => 'participant']);
        $this->isOnAnyBriefTeam = true;

        $this->dispatch('add-member');
    }
};

$removeTeamMember = function ($userId) {
    $teamUser = $this->team->users()->where('user_id', $userId)->first();
    if ($teamUser->pivot->role !== 'owner') {
        $this->team->users()->detach($userId);
        $user = User::find($userId);
        // event(new TeamMemberDeleted($this->team, $user));
        $this->dispatch('remove-member');
    }
};

$setNewOwner = function () {
    $user = auth()->user();

    $teamUser = $this->team
        ->users()
        ->where('user_id', $user->id)
        ->first();
    $newOwner = $this->team->users()->first();

    if ($teamUser->pivot->role === 'owner') {
        $this->team->users()->updateExistingPivot($newOwner->id, ['role' => 'owner']);
    }
};

$leaveTeam = function () {
    $this->setNewOwner();
    $this->team->users()->detach(auth()->user()->id);
    $this->isAlreadyOnTeam = false;
    $this->dispatch('remove-member');
};

$isAlreadyOnTeam = computed(function () {
    return $this->team
        ->users()
        ->where('user_id', auth()->user()->id)
        ->exists();
});

$isFinalist = computed(function () {
    return $this->team->finalist;
});

$isWinner = computed(function () {
    return $this->team->winner;
});

$isOnAnyBriefTeam = computed(function () {
    $teamIds = $this->brief->teams()->pluck('id');

    $userInTeam = DB::connection('mysql')
        ->table('team_user')
        ->where('user_id', auth()->user()->id)
        ->whereIn('team_id', $teamIds)
        ->exists();

    return $userInTeam;
});

$deleteTeam = function () {
    $this->dispatch('team-deleted', $this->team->id); 
};

$currentRouteIsEdit = computed(function () {
    return ($this->crud && auth()->user()->role === 'ADMIN') || auth()->user()->role === 'MANAGER';
});

?>
<div x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false"
    class="relative flex flex-col justify-evenly w-[30%] bg-shakers-red rounded-xl p-4 @if ($this->currentRouteIsEdit) cursor-pointer @endif">
    @unless (!$this->currentRouteIsEdit || !auth()->user()->role === 'ADMIN' || !auth()->user()->role === 'MANAGER')
        <div x-show="open" x-transition:enter="transition ease-out duration-200"
            x-transition:enter-start="transform opacity-0 -translate-y-2"
            x-transition:enter-end="transform opacity-100 translate-y-0" x-transition:leave="transition ease-in duration-150"
            x-transition:leave-start="transform opacity-100 translate-y-0"
            x-transition:leave-end="transform opacity-0 -translate-y-2"
            class="absolute -top-4 flex items-center justify-center w-full p-2 rounded-lg">
            <button wire:click="openEditTeamModal" class="bg-white p-2 rounded-lg">
                <x-heroicon-c-pencil-square class="size-10 text-black" />
            </button>
        </div>
    @endunless
    <x-mary-modal wire:model="editModal">
        <x-slot:actions>
            <x-mary-button icon="o-x-mark" spinner wire:click='closeEditTeamModal'
                class="absolute text-black bg-white rounded-full top-5 right-5 btn-sm hover:bg-slate-200" />
        </x-slot:actions>
        <livewire:create-team-form :$brief />
    </x-mary-modal>
    <x-mary-modal wire:model="editModal">
        <x-slot:actions>
            <x-mary-button icon="o-x-mark" spinner wire:click='closeEditTeamModal'
                class="absolute text-black bg-white rounded-full top-5 right-5 btn-sm hover:bg-slate-200" />
        </x-slot:actions>
        <livewire:edit-team-form :$brief :$team />
    </x-mary-modal>
    @if ($this->currentRouteIsEdit)
        <div class="absolute -top-5 -right-5">
            <x-mary-button icon="o-x-mark" wire:confirm="Estas seguro que quieres eliminar este team?" spinner
                wire:click='deleteTeam' class="text-black bg-white rounded-full btn-sm hover:bg-slate-200" />
        </div>
    @endif
    <div class="flex items-center justify-between">
        <h6>EQUIPO</h6>
        <x-team-diamonds-form />
    </div>
    <h4>{{ $team->name }}</h4>
    <ul class="px-8 py-4 w-[100%] list-disc">
        @foreach ($this->sortedTeamMembers as $member)
            @if (auth()->user()->role == 'ADMIN' || auth()->user()->role == 'MANAGER')
                <div class="flex items-center w-full gap-x-2" x-data="{ showButton: false }">
                    @if ($member->pivot->role === 'owner')
                        <li class="uppercase text-lg mb-[2px] flex items-center gap-x-2" @mouseover="showButton = true"
                            @mouseout="showButton = false">
                            {{ $member->first_name . ' ' . $member->last_name }}
                            <x-icons.userLeaderIcon class="size-5" />
                        </li>
                    @else
                        <li class="text-xl font-thin tracking-tight uppercase text-shakers-white"
                            @mouseover="showButton = true" @mouseout="showButton = false">
                            {{ $member->first_name . ' ' . $member->last_name }}
                            @if ($this->currentRouteIsEdit)
                                <x-mary-button id="trash"
                                    wire:confirm="Estas seguro que quieres eliminar este miembro del equipo?"
                                    wire:click="removeTeamMember({{ $member->id }})"
                                    class="text-white border-none bg-transparent shadow-none hover:bg-transparent hover:border-none hover:translate-x-0.5 transition ease-in-out"
                                    x-show="showButton">
                                    <x-icons.trashIcon class="size-5" />
                                </x-mary-button>
                            @endif
                        </li>
                    @endif
                </div>
            @else
                <div class="flex items-center w-full gap-x-2" x-data="{ showButton: false }">
                    @if ($member->pivot->role === 'owner')
                        <li class="uppercase text-lg mb-[2px] flex items-center gap-x-2">
                            {{ $member->first_name . ' ' . $member->last_name }}
                            <x-icons.userLeaderIcon class="size-5" />
                            @if ($this->currentRouteIsEdit)
                                <x-mary-button id="trash"
                                    wire:confirm="Estas seguro que quieres eliminar este miembro del equipo?"
                                    wire:click="removeTeamMember({{ $member->id }})"
                                    class="text-white border-none bg-transparent shadow-none hover:bg-transparent hover:border-none hover:translate-x-0.5 transition ease-in-out"
                                    x-show="showButton">
                                    <x-icons.trashIcon class="size-5" />
                                </x-mary-button>
                            @endif
                        </li>
                    @else
                        <li class="text-xl font-thin tracking-tight uppercase text-shakers-white">
                            {{ $member->first_name . ' ' . $member->last_name }}
                            @if ($this->currentRouteIsEdit)
                                <x-mary-button id="trash"
                                    wire:confirm="Estas seguro que quieres eliminar este miembro del equipo?"
                                    wire:click="removeTeamMember({{ $member->id }})"
                                    class="text-white border-none bg-transparent shadow-none hover:bg-transparent hover:border-none hover:translate-x-0.5 transition ease-in-out"
                                    x-show="showButton">
                                    <x-icons.trashIcon class="size-5" />
                                </x-mary-button>
                            @endif
                        </li>
                    @endif
                </div>
            @endif
        @endforeach
    </ul>
    @if ($this->isAlreadyOnTeam)
        @unless ($this->briefTimeHasFinished)
            <x-mary-button
                class="flex items-center justify-center px-4 mt-4 font-thin text-center text-white transition ease-in-out rounded-full gap-x-2 hover:translate-y-1"
                wire:confirm="Seguro que quieres abandonar el equipo?" wire:click="leaveTeam">Abandonar
                <x-heroicon-s-arrow-left-end-on-rectangle class="size-6" />
            </x-mary-button>
        @endunless
    @elseif(!$this->isOnAnyBriefTeam)
        @unless ($this->briefTimeHasFinished)
            <x-mary-button class="btn" wire:confirm="Estas seguro que quieres unirte a este equipo?"
                wire:click="addTeamMember"><span class="flex items-center justify-center">Unirse
                    <x-heroicon-s-arrow-right-end-on-rectangle class="size-6" />
                </span>
            </x-mary-button>
        @endunless
    @endif
</div>

Edit.blade livewire component (parent component)

<?php

use function Livewire\Volt\{state, rules, mount, on, computed, uses};

use App\Models\Client;
use App\Models\Brief;
use App\Models\Icon;
use App\Livewire\Forms\BriefForm;
use Livewire\Volt\Component;
use Illuminate\Support\Collection;
use Illuminate\Support\Carbon;
use App\Models\Team;
use App\Events\TeamIsWinner;
use App\Events\TeamIsFinalist;
use App\Actions\Briefs\GetStatusesAction;
use Mary\Traits\Toast;

uses([Toast::class]);

state(['brief', 'title' => '', 'client_id' => null, 'context' => '', 'problem' => '', 'objective' => '', 'deadline' => null, 'status' => '', 'icon_id' => null, 'materials_url' => null, 'teams', 'crud' => 'edit', 'client_searchable_id' => null, 'clientSearchable' => collect(), 'icons' => collect(), 'statuses' => []]);

mount(function (Brief $brief) {
    $action = new GetStatusesAction();
    $this->statuses = $action->handle();
    $this->status = $this->brief->status;
    $this->title = $this->brief->title;
    $this->client_id = $this->brief->client_id;
    $this->context = $this->brief->context;
    $this->problem = $this->brief->problem;
    $this->objective = $this->brief->objective;
    $this->deadline = $this->brief->deadline;
    $this->icon_id = $this->brief->icon_id;
    $this->materials_url = $this->brief->materials_url;
    $this->teams = $this->brief->teams;
    $this->search();
    $this->icons = Icon::all();
});

on([
    'team-deleted' => function ($teamId) {
        $team = Team::find($teamId);
        $team->delete();
        // $this->teams = Brief::find($this->brief->id)->teams;
    },
]);

$search = function (string $value = '') {
    $selectedOption = Client::where('id', $this->client_searchable_id)->get();

    $this->clientSearchable = Client::query()
        ->where('name', 'like', "%$value%")
        ->orderBy('name')
        ->get()
        ->merge($selectedOption); // <-- Adds selected option
};

?>
<div>
    <div class="flex flex-wrap gap-10 py-12 border-b border-white/10">
        @if ($this->teams->isEmpty())
        <h2 class="w-full text-3xl font-semibold text-center text-shakers-white">NO HAY EQUIPOS TODAVÍA</h2>
        @else
        @foreach ($teams as $team)
        <livewire:teams.show-team 
                                  :key="$team->id"
                                  :team="$team"
                                  :brief="$brief"
                                  :isAdmin=true
                                  :crud="$crud" />
        @endforeach
        @endif
    </div>
</div>

Tried to isolate as much as I could, this is the edit view where the $brief comes from, the edit view url it looks for the $brief->id (briefs/edit/2), and the 'teams' comes from $brief->teams (as you can see on mount $this->teams = $this->brief->teams

This is the edit.blade.phpview where the edit livewire component is rendered

<x-layouts.site :brief="$brief">
    <div  class="mx-auto p-4 sm:p-6 lg:p-8 w-[60vw]">
        <livewire:edit :$brief crud="edit" />
    </div>
</x-layouts.site>

Thank you guys for the interest & help on this, i'll continue to isolate more and more once I have more time. I'll add also now the network errors, because the team gets deleted, but it does a second network update where it fails. That's where I guess it's looking for the team id that just got deleted. 🫶

0 likes
7 replies
Snapey's avatar

404 because you are still trying to show the deleted member?

1 like
ignaMani's avatar

@Snapey Yeah that's what I'm trying to solve, when I do the

on([
    'team-deleted' => function ($teamId) {
            $team = Team::find($teamId);
            $team->delete();
    },
]);

once the $team->delete() is done it tries to find that deleted team again (I guess that tries to find that team again on the @foreach loop) Or maybe I'm wrong? I've been trying again and again but it's always trying to search that deleted team again once it's deleted.

Thanks for the answer!

ignaMani's avatar

And also the Team::find($teamId) is being found, so I dont really know where exactly is trying to find that deleted team once delete() method is done.

ignaMani's avatar

@Snapey I isolated the code a bit, will be isolating it more once I have more time.

robsontenorio's avatar

I would advice you to remove extra “noise code” and try to isolate the issue.

It is hard to help you , because there a lot of things happening on same component.

If I could guess it is because you are trying to delete a object/model that rules this route through /xxxx/{id}.

And that object is passed down to child components.

1 like
ignaMani's avatar

@robsontenorio Thanks for the answer will isolate the issue as much as I can and see what is really happening because I'm turning crazy with this 🤣, will update this if I isolate it and see it stills happening ✌️🫶

Please or to participate in this conversation.