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

sshateri's avatar

$loop->index messes up the other foreach

I have 2 separate foreach loops in my view which I use $loop->index for wire:key value to keep track of the components during adding and deleting them. when clicking the second button to add the second component it seems the $loop->index in the second foreach turns the other $loop->index to zero which is odd since the $loop->index supposed to return The index of the current loop iteration and not have an effect on the other ones.

any idea whats going on and how can i fix this?

first loop:

@foreach ($languages as $language)
    <div class="card card-body mb-4" wire:key="{{ $loop->index }}">
        <div class="text-left"><span class="fa fa-trash text-gray language-delete" wire:click="removeLanguage({{ $loop->index }}, {{ !empty($language['id']) ? $language['id'] : 0 }})"></span></div>
        <div class="row">
            <div class="form-group col-md-6">
                <label class="" for="languageName">زبان</label>
                <select class="form-control form-control-alternative" name="language-name" {{-- id="languageName" --}} wire:model="languages.{{ $loop->index }}.language_name">
                    <option value="" class="form-control" selected disabled>انتخاب زبان</option>
                    @foreach ($language_names as $name)
                        <option value="{{ $name->abbreviation }}" class="form-control">{{ $name->language }}</option>
                    @endforeach
                </select>
            </div>
            <div class="form-group col-md-6">
                <label class="" for="languageProficiency">میزان تسلط</label>
                <select class="form-control form-control-alternative" name="language-proficiency" {{-- id="languageProficiency" --}} wire:model="languages.{{ $loop->index }}.language_level">
                    <option value="" class="form-control" selected disabled>میزان تسلط</option>
                    @foreach ($language_levels as $level)
                        <option value="{{ $level->level }}" class="form-control">{{ $level->name }}</option>
                    @endforeach
                </select>
            </div>
        </div>
    </div>
@endforeach

@error('languages.*.language_level')
    <small class="text-warning">{{ $message }}</small>
@enderror
@error('languages.*.language_language')
    <small class="text-warning">{{ $message }}</small>
@enderror

@if (count($languages) < 3)
    <div class="row">
        <div class="col-md-12">
            <button type="button" class="btn btn-outline-secondary btn-round btn-block" wire:click="addLanguage"><span class="btn-inner--icon"><i class="fa fa-plus fa-2x"></i></span></button>
        </div>
    </div>
@endif

2nd loop:

@foreach ($certificates as $certificate)
    <div class="card card-body mb-4" wire:key="{{ $loop->index }}">
        <div class="row">
            <div class="form-group col-md-6">
                <label class="" for="other-certificates-name">نام مدرک</label>
                <input type="text" class="form-control form-control-alternative" placeholder="" id="other-certificates-name" name="ptherCertificatesName">
            </div>
            <div class="form-group col-md-6">
                <label class="" for="other-certificates-school-name">نام مرکز آموزشی</label>
                <input type="text" class="form-control form-control-alternative" placeholder="" id="other-certificates-school-name" name="otherCertificatesSchoolName">
            </div>
            <div class="form-group col-md-6">
                <label class="" for="other-certificates-verification-link">لینک تایید <small>(اختیاری)</small></label>
                <input type="text" class="form-control form-control-alternative text-left" placeholder="https://devindex.ir" id="other-certificates-verification-link" name="otherCertificatesVerificationLink">
            </div>
            <div class="form-group col" dir="ltr">
                <label class="" for="other-certificates-grad-date" dir="rtl">تاریخ اخذ</label>
                <div class="input-group input-group-alternative">
                    <div class="input-group-prepend">
                    <span class="input-group-text"><i class="ni ni-calendar-grid-58"></i></span>
                    </div>
                    <input type="text" class="form-control form-control-alternative datePicker" placeholder="" id="other-certificates-grad-date" name="otherCertificatesGradDate" value="">
                </div>
            </div>
        </div>
    </div>
@endforeach
@if (count($certificates) < 5)
    <div class="row">
        <div class="col-md-12">
        <button type="button" class="btn btn-outline-secondary btn-round btn-block" wire:click="addCertificate"><span class="btn-inner--icon"><i class="fa fa-plus fa-2x"></i></span></button>
        </div>
    </div>
@endif
0 likes
13 replies
automica's avatar

@sshateri not quite sure how those two loops relate to each other, but if you want to circumnavigate the issue, just use an index in your foreach:

@foreach ($languages as $lindex => $language)
    <div class="card card-body mb-4" wire:key="{{ $lindex }}">

and

@foreach ($certificates as $cindex => $certificate)
   <div class="card card-body mb-4" wire:key="{{ $cindex }}">
1 like
sshateri's avatar

that's what exactly is confusing me because those are two separate loops in a view. it's very odd.

automica's avatar

did you change:

wire:model="languages.{{ $loop->index }}.language_level"

too?

I'm having some trouble understanding whats going on in your screen recording due to the non-English labels.

Can you post your livewire component so I can see whats going on with both your add* methods?

sshateri's avatar

Yes i did. here is the changed code:

<!-- language section -->
<div class="card card-profile shadow-sm mt-4">
    <div class="px-4 mt-4 mb-4">
        <div class="h5 font-weight-bold mb-4">زبانهای خارجی</div>
        <div class="heading text-muted mb-4">تنها مجاز به اضافه کردن ۳ زبان به پروفابل خود هستید.</div>
            @foreach ($languages as $lindex => $language)
                <div class="card card-body mb-4" wire:key="{{ $lindex }}">
                    <div class="text-left"><span class="fa fa-trash text-gray language-delete" wire:click="removeLanguage({{ $lindex }}, {{ !empty($language['id']) ? $language['id'] : 0 }})"></span></div>
                    <div class="row">
                        <div class="form-group col-md-6">
                            <label class="" for="languageName">زبان</label>
                            <select class="form-control form-control-alternative" name="language-name" {{-- id="languageName" --}} wire:model="languages.{{ $lindex }}.language_name">
                                <option value="" class="form-control" selected disabled>انتخاب زبان</option>
                                @foreach ($language_names as $name)
                                    <option value="{{ $name->abbreviation }}" class="form-control">{{ $name->language }}</option>
                                @endforeach
                            </select>
                        </div>
                        <div class="form-group col-md-6">
                            <label class="" for="languageProficiency">میزان تسلط</label>
                            <select class="form-control form-control-alternative" name="language-proficiency" {{-- id="languageProficiency" --}} wire:model="languages.{{ $lindex }}.language_level">
                                <option value="" class="form-control" selected disabled>میزان تسلط</option>
                                @foreach ($language_levels as $level)
                                    <option value="{{ $level->level }}" class="form-control">{{ $level->name }}</option>
                                @endforeach
                            </select>
                        </div>
                    </div>
                </div>
            @endforeach

            @error('languages.*.language_level')
                <small class="text-warning">{{ $message }}</small>
            @enderror
            @error('languages.*.language_language')
                <small class="text-warning">{{ $message }}</small>
            @enderror

            @if (count($languages) < 3)
                <div class="row">
                    <div class="col-md-12">
                        <button type="button" class="btn btn-outline-secondary btn-round btn-block" wire:click="addLanguage"><span class="btn-inner--icon"><i class="fa fa-plus fa-2x"></i></span></button>
                    </div>
                </div>
            @endif

    </div>
</div>
<!-- end language section -->

<!-- other certificates section -->
<div class="card card-profile shadow-sm mt-4">
    <div class="px-4 mt-4 mb-4">
        <div class="h5 font-weight-bold mb-4">دیگر مدارک مرتبط </div>
        <div class="heading text-muted mb-4">اگر مدارک دیگری در زمینه کاری خود دارید در زیر وارد کنید.</div>
        @foreach ($certificates as $cindex => $certificate)
            <div class="card card-body mb-4" wire:key="{{ $cindex }}">
                <div class="row">
                    <div class="form-group col-md-6">
                        <label class="" for="other-certificates-name">نام مدرک</label>
                        <input type="text" class="form-control form-control-alternative" placeholder="" name="ptherCertificatesName">
                    </div>
                    <div class="form-group col-md-6">
                        <label class="" for="other-certificates-school-name">نام مرکز آموزشی</label>
                        <input type="text" class="form-control form-control-alternative" placeholder="" name="otherCertificatesSchoolName">
                    </div>
                    <div class="form-group col-md-6">
                        <label class="" for="other-certificates-verification-link">لینک تایید <small>(اختیاری)</small></label>
                        <input type="text" class="form-control form-control-alternative text-left" placeholder="" name="otherCertificatesVerificationLink">
                    </div>
                    <div class="form-group col" dir="ltr">
                        <label class="" for="other-certificates-grad-date" dir="rtl">تاریخ اخذ</label>
                        <div class="input-group input-group-alternative">
                            <div class="input-group-prepend">
                            <span class="input-group-text"><i class="ni ni-calendar-grid-58"></i></span>
                            </div>
                            <input type="text" class="form-control form-control-alternative datePicker" placeholder="" name="otherCertificatesGradDate" value="">
                        </div>
                    </div>
                </div>
            </div>
        @endforeach
        @if (count($certificates) < 5)
            <div class="row">
                <div class="col-md-12">
                <button type="button" class="btn btn-outline-secondary btn-round btn-block" wire:click="addCertificate"><span class="btn-inner--icon"><i class="fa fa-plus fa-2x"></i></span></button>
                </div>
            </div>
        @endif
    </div>
</div>
<!-- end other certificates section -->

perhaps the issue is not what i thought might be.

sshateri's avatar

Here is the livewire component code maybe your eyes will catch something that mine didn't.

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\CityName;
use App\Models\Language;
use App\Models\LanguageLevel;
use App\Models\User;
use App\Models\UserLanguage;
use App\Models\UserProfile;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;

class UserInfo extends Component
{
    public $name;
    public $email;
    public $phone;
    public $states = [];
    public $selectedstate;
    public $cities = [];
    public $selectedcity;
    public $jobTitle;
    public $aboutMe;

    public $employer;
    public $position;

    public $edu_course;
    public $edu_school;
    public $edu_level;
    public $edu_start_date;
    public $edu_end_date;

    public $employment_looking;
    public $employment_hired;

    public $twitter;
    public $linkedin;
    public $github;
    public $instagram;
    public $website;

    public $languages = [];
    public $language_names;
    public $language_levels;
    public $languageToDelete = [];

    public $certificates = [];
    public $certificate_name;
    public $certificate_school;
    public $certificate_link;
    public $certificate_date;
    public $certificateToDelete = [];

    public $skillsQuery;
    public $skillResults;

    public $skills;





    public function render()
    {
        /* $this->retriveStates();
        $this->retriveCities();
        $this->retriveLanguages(); */
        return view('livewire.user-info');
    }



    public function retriveStates()
    {
        $this->states = CityName::distinct()->get(['state']);
    }


    public function retriveCities()
    {
        $this->cities = CityName::where('state', $this->selectedstate)->get(['city']);
    }


    public function updatedSkillsQuery()
    {
        $this->skillResults = $this->skills->where('name', 'like', $this->skillsQuery) ?? collect();
    }


    public function retriveLanguages()
    {
        $this->language_names = Language::all();
        $this->language_levels = LanguageLevel::all();
    }


    public function addLanguage()
    {
        if (count($this->languages) <= 3)
        {
            array_push($this->languages, ['language_name'=>'', 'language_level'=>'', 'id'=>'']);
        }
        else
        {
            $this->sweetAlert('error', 'تنها مجاز به اضافه کردن ۳ زبان به پروفابل خود هستید.');
        }
    }


    public function getUserLanguages()
    {
        $this->languages = auth()->user()->userLanguages->toArray();
    }


    public function removeLanguage($languagePosition, $languageId)
    {
        if (isset($languageId))
        {
            if ($languageId == 0)
            {
                array_splice($this->languages, $languagePosition, 1);
            }
            else
            {
                array_push($this->languageToDelete, $languageId);
                array_splice($this->languages, $languagePosition, 1);
            }
        }
    }


    public function addCertificate()
    {
        if (count($this->certificates) <= 5)
        {
            array_push($this->certificates, ['name'=>'', 'school'=>'', 'link'=>'', 'date'=>'', 'id'=>'']);
            /* dd($this->languages); */
        }
        else
        {
            $this->sweetAlert('error', 'تنها مجاز به اضافه کردن ۵ زبان به پروفابل خود هستید.');
        }
    }


    public function mount()
    {

        $this->skills = collect([
            ['name' => 'vue'],
            ['name' => 'vue'],
            ['name' => 'vue'],
            ['name' => 'laravel'],
            ['name' => 'laravel'],
            ['name' => 'laravel'],
        ]);

        $this->skillResults= [];
        $this->skillsQuery = '';
        $this->retriveLanguages();
        $this->retriveStates();
        $this->retriveCities();
        $this->getUserLanguages();
        $this->name = auth()->user()->name;
        $this->email = auth()->user()->email;
        $this->phone = auth()->user()->phone;
        $this->selectedstate = auth()->user()->userprofile->state;
        $this->selectedcity = auth()->user()->userprofile->city;
        $this->jobTitle = auth()->user()->userprofile->job_title;
        $this->aboutMe = auth()->user()->userprofile->about_me;
        $this->employer = auth()->user()->userprofile->employer;
        $this->position = auth()->user()->userprofile->position;
        $this->edu_course = auth()->user()->userprofile->edu_course;
        $this->edu_school = auth()->user()->userprofile->edu_school;
        $this->edu_level = auth()->user()->userprofile->edu_level;
        $this->edu_start_date = auth()->user()->userprofile->edu_start_date;
        $this->edu_end_date = auth()->user()->userprofile->edu_end_date;
        $this->employment_looking = auth()->user()->userprofile->employment_looking;
        $this->employment_hired = auth()->user()->userprofile->employment_hired;
        $this->twitter = auth()->user()->userprofile->twitter;
        $this->linkedin = auth()->user()->userprofile->linkedin;
        $this->github = auth()->user()->userprofile->github;
        $this->instagram = auth()->user()->userprofile->instagram;
        $this->website = auth()->user()->userprofile->website;

    }


    public function rules()
    {
        return [
            'name' => ['required', 'string', 'max:250'],
            'email' => [
                'required',
                'email',
                'max:250',
                Rule::unique('users')->ignore(auth()->id()),
            ],
            'phone' => ['required', 'digits:11'],
            'selectedstate' => 'required',
            'selectedcity' => 'required',
            'jobTitle' => ['required', 'string', 'max:250'],
            'aboutMe' => ['required', 'string', 'max:250'],
            'employer' => ['string', 'max:250'],
            'position' => ['string', 'max:250'],
            'edu_course' => ['nullable', 'string', 'max:250'],
            'edu_school' => ['nullable', 'string', 'max:250'],
            'edu_level' => ['nullable', 'string', 'max:250'],
            'edu_start_date' => ['nullable', 'string'],
            'edu_end_date' => ['nullable', 'string'],
            'employment_looking' => ['nullable', 'boolean'],
            'employment_hired' => ['nullable', 'boolean'],
            'twitter' => ['nullable', 'string', 'max:250'],
            'linkedin' => ['nullable', 'string', 'max:250'],
            'github' => ['nullable', 'string', 'max:250'],
            'instagram' => ['nullable', 'string', 'max:250'],
            'website' => ['nullable', 'string', 'max:250'],
            'languages.*.language_name' => ['required', 'exists:App\Models\Language,abbreviation'],
            'languages.*.language_level' => ['required', 'exists:App\Models\LanguageLevel,level'],
        ];
    }


    public function submit()
    {
        $user = Auth::user();

        $this->validate();

        User::where('id', auth()->id())->update([
            'name' => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
        ]);

        UserProfile::where('user_id', auth()->id())->update([
            'state' => $this->selectedstate,
            'city' => $this->selectedcity,
            'job_title' => $this->jobTitle,
            'about_me' => $this->aboutMe,
            'employer' => $this->employer,
            'position' => $this->position,
            'edu_course' => $this->edu_course,
            'edu_school' => $this->edu_school,
            'edu_level' => $this->edu_level,
            'edu_start_date' => $this->edu_start_date,
            'edu_end_date' => $this->edu_end_date,
            'employment_looking' => $this->employment_looking,
            'employment_hired' => $this->employment_hired,
            'twitter' => $this->twitter,
            'linkedin' => $this->linkedin,
            'github' => $this->github,
            'instagram' => $this->instagram,
            'website' => $this->website,
        ]);


        if (!empty($this->languageToDelete))
        {
            /* $user = Auth::user(); */
            foreach ($this->languageToDelete as $delete)
            {
                $user->userLanguages()->where('id', $delete)->delete();
            }
        }

        foreach ($this->languages as $language)
        {
            /* $user = Auth::user(); */
            $user->userLanguages()->updateOrCreate([
                'language_name' => $language['language_name'],
            ],
            [
                'language_name' => $language['language_name'],
                'language_level' => $language['language_level']
            ]
            );

        }

        $this->getUserLanguages();

        $this->sweetAlert('success', 'تغییرات با موفقیت ذخیره شد!');

    }


    public function sweetAlert($type, $message)
    {
        $this->alert($type, $message, [
            'position'  =>  'bottom-end',
            'timer'  =>  5000,
            'toast'  =>  true,
            'text' => null,
            'showCancelButton'  =>  false,
            'showConfirmButton'  =>  false
        ]);
    }

}
automica's avatar

@sshateri rather than pass in the position of the language in the array, wouldn't it be better to remove it by its id

eg

wire:click="removeLanguage({{ $language['id'] }}

that way irrespective of how your array is ordered, you'll be able to delete the correct one

public function removeLanguage($languageId)
{
	$this->languages = $this->languages->where('id', '!=', $languageId);
}
1 like
sshateri's avatar

You are absolutely right and that's the path I took at first but the issue was when you add a new language and decide to not save it to the database you won't be able to remove it from the form/view since it doesn't have any id yet and also it is not in the DB yet. so that's why I invented this odd solution.

Regardless I don't think it has something to do with the issue I'm having right now? or does it?

sshateri's avatar

the issue is very odd because it seems only the first language will disappear and then after another render it will appear again. I made another screen record to show it. hope it will help. i tried moving things around from calling the functions I was suspected to from mount to render and also tried other lifecycle hooks like hydrate, dehydrate, update, updated but no luck.

https://drive.google.com/file/d/1lo4FNwHtu56rHQMM-1gQ4nbDnLM-dJLi/view?usp=sharing

Also, something else I noticed is that after adding a new language section or any other sections dynamically the fields that I defined as date fields using flatpickr will not pop the date picker anymore until I refresh the page. not sure what is causing this issue.

automica's avatar

@sshateri I'm still stuck with your video as I can't read arabic and don't know which bit you are clicking and the section that is updating.

WRT languages, this is a list of language names and their respective level? That would mean your array key could be a compound string of

$language_name.'-'.$language_level

and that would mean that you can check for duplication and we have unique identifier and decouples from saved id.

Regarding your data picker field issue, I've not used flatpickr so dont know how well it plays with livewire. What you are describing seems related to the library picking up that the DOM has refreshed.

Looking at livewire docs, https://laravel-livewire.com/docs/2.x/alpine-js -> theres a section covering building a datepicker component so it might be worth looking at that as a way to solve your issues.

sshateri's avatar

Sorry for the language barrier. It's Persian by the way. I have added some English labels for those two sections so it may be less confusing and I recorded it again.

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

I understand what are you saying regarding the language and the language level but there are some things that need to mention.

1- changes are not persisted to the DB in real-time and there is a save button at the bottom of the page to persist the changes to the DB.

2- imagine this scenario: I as a user decide to add English and Spanish to my spoken language section. I add the English and the corresponding level of proficiency and then Spanish. then I decide I will not add Spanish to my profile so I decided to remove it. if we go with the delete by ID solution it is not possible to do so since the changes are not persisted in the DB yet therefore there is no ID for the added Spanish language yet because the ID field is auto-generated by the DB. that's why I came up with the array solution to be able to remove the recently added language and not yet saved in DB language.

automica's avatar

@sshateri my suggestion was to make the keys in your language array not reliant on id but as a composite of the language and level

eg

$languages = [
'ENGLISH-HIGHER' => [ 'language_name'=>'English', 'proficiency_level'=>'Higher'],
'FRENCH-BASIC' => [ 'language_name'=>'French', 'proficiency_level'=>'Basic']
]

That gives you unique id's without worrying about the actual id from the database.

1 like
sshateri's avatar
sshateri
OP
Best Answer
Level 3

That's a good one. I'm sure there are many ways to improve my code but the problem right now is the disappearance of the language section upon adding a new certificate section.

Please or to participate in this conversation.