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

SigalZ's avatar

Livewire save fields with same name

Hello,

Livewire is still new to me and I'm trying something that is beyond my understanding.

Maybe you can help.

I have 3 tables: jobs, spares, job_spare

When I create or edit a job I need to add the spares that were used for this job.

I have a view where I have 2 tables, one table holds the spares that were used and the other table holds all the spares that were not used in the job.

On the second table, on each row I have the spare name, an input field for Quantity and an add button.

The code:

<div class="input-group mb-3">
    <input type="text" class="form-control" placeholder="Quantity" aria-label="Quantity" aria-describedby="button-addon-quantity" wire:model.lazy="qty">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="button" id="button-addon-quantity" wire:click.prevent="addSpare({{$model->id}})">Add</button>
    </div>
  </div>

In the livewire component I have this function:

protected $rules =  ['qty'=>'numeric'];

public function addSpare($spareId)
    {
        $this->validate();
        
        $this->job->spares()->attach($spareId, ['qty' => $this->qty]);
        return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2');
    }

The problem is, if I do not capture the qty, and press the Add button, all the qty fields show the red border for errors.

I hope you understand what I'm trying to do and help.

0 likes
9 replies
rodrigo.pedra's avatar

If you have several inputs into the same form, you should map each input to its own "variable".

Try this:

<div class="input-group mb-3">
    <input type="text" class="form-control" placeholder="Quantity" aria-label="Quantity" aria-describedby="button-addon-quantity" wire:model.lazy="qty.{{$model->id}}">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="button" id="button-addon-quantity" wire:click.prevent="addSpare({{$model->id}})">Add</button>
    </div>
  </div>

Note I added the model id as a nested property using the dot notation

protected $rules =  ['qty'=>'array', 'qty.*' => 'numeric'];

public function addSpare($spareId)
{
    $this->validate();
    
    $this->job->spares()->attach($spareId, ['qty' => $this->qty[$spareId]]);

    return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2');
}

You might need to declare the $qty property on this component as an array.

Reference:

https://laravel-livewire.com/docs/2.x/properties#binding-nested-data

SigalZ's avatar

@rodrigo.pedra Thank you. I tried your solution but it doesn't work.

I tried a few things:

The component:

 protected $rules = ['qty'=>'array', 'qty.*'=>'numeric'];
    protected $messages = ['qty.*.numeric'=>'Please enter the quantity'];

My input field:

<input type="text" class="form-control @error('qty.{{$model->id}}') is-invalid @enderror"
     wire:model.lazy="qty.{{$model->id}}">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="button" id="button-addon-quantity" wire:click.prevent="addSpare({{$model->id}})">Add</button>
    </div>
    <x-display_error error="qty{{$model->id}}"/>
  </div>

No errors displayed now.

I tried:

<input type="text" class="form-control @error('qty') is-invalid @enderror"
     wire:model.lazy="qty.{{$model->id}}">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="button" id="button-addon-quantity" wire:click.prevent="addSpare({{$model->id}})">Add</button>
    </div>
    <x-display_error error="qty"/>
  </div>

All inputs get the error: The qty must be an array.

I also tried a few other things:

public function addSpare($spareId)
{
   if(empty($this->qty))
      $this->addError('qty.'.$spareId, 'Qty empty');
      
return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2']);

Didn't get the errors and it tried to do this:

$this->job->spares()->attach($spareId, ['qty' => $this->qty[$spareId]]);

which of course gave the error: Undefined offset: 13

I tried:

 public function addSpare($spareId)
    {
        $errors = $this->getErrorBag();
        if(empty($this->qty))
        
        $errors->add('qty.'.$spareId, 'Some message');
        
        $this->job->spares()->attach($spareId, ['qty' => $this->qty[$spareId]]);
        return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2']);
    }

Again, tried to do the attach part and gave me this error: Trying to access array offset on value of type null

I tried:

public function addSpare($spareId)
    {
       
        
        $this->withValidator(function (Validator $validator) use($spareId) {
            $validator->after(function ($validator) use($spareId){
                if (empty($this->qty)) {
                    $validator->errors()->add('qty.'.$spareId, 'Something is wrong with this field!');
                }
            });
        })->validate();

       $this->job->spares()->attach($spareId, ['qty' => $this->qty[$spareId]]);
        return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2']);
}

Didn't get any errors.

I ended up doing this:

public function addSpare($spareId)
    {
        if(empty($this->qty[$spareId)) {
            alert()->error('Error!', 'Please enter the quantity');
        } elseif(!is_numeric($this->qty[$spareId])) {
            alert()->error('Error!', 'The quanity must be a number');
        } else {
            $this->job->spares()->attach($spareId, ['qty' => $this->qty[$spareId]]);
        }

        return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2']);
    }

Lost the will to live LOL

Thanks for trying to help.

rodrigo.pedra's avatar

@SigalZ I tested it with this code:

<?php

namespace App\Http\Livewire;

use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Component;

class Sample extends Component
{
    public $qty = [];

    public Collection $models;

    protected $rules = [
        'qty' => 'array',
        'qty.*' => 'numeric',
    ];

    protected $messages = [
        'qty.*.numeric' => 'Please enter the quantity',
    ];

    public function mount()
    {
        $this->models = User::query()
            ->take(10)
            ->get();
    }

    public function addSpare($spareId)
    {
        $this->validate();

        logger()->info('add spare', ['qty' => $this->qty[$spareId] ?? 0]);
        // return Redirect::back();
    }

    public function render()
    {
        return view('livewire.sample');
    }
}
<div>
    @foreach($models as $model)
        <div class="input-group">
            <input
                type="text"
                class="form-control @error('qty.' . $model->id) is-invalid @enderror"
                wire:model.lazy="qty.{{ $model->id }}">

            <div class="input-group-append">
                <button
                    class="btn btn-outline-secondary"
                    type="button"
                    id="button-addon-quantity"
                    wire:click.prevent="addSpare({{ $model->id }})">Add
                </button>
            </div>
        </div>


        @error('qty.' . $model->id)
        <p>{{ $message }}</p>
        @enderror
    @endforeach

    <ul>
        @forelse($qty as $key => $value)
            <li><strong>{{ $key }}:</strong> {{ $value }}</li>
        @empty
            <li><em>empty</em></li>
        @endforelse
    </ul>
</div>
<?php // ./routes/web.php

use Illuminate\Support\Facades\Route;

Route::view('/', 'welcome');
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Sample</title>

    @livewireStyles
</head>
<body>
    <livewire:sample></livewire:sample>

    @livewireScripts
</body>
</html>

Note I added 10 users using the User Factory with the database seeder.

SigalZ's avatar

@rodrigo.pedra Thank you. Tried that. I get the log: local.INFO: add spare {"qty":0} but no errors on the view. so I'm not sure how it works for you.

Do you see the error on the view and only for the field you tried to add?

rodrigo.pedra's avatar

@SigalZ no errors, and also I got the <ul> list updated as I add values. Will send a screenshot soon, I am about to have lunch

SigalZ's avatar

@rodrigo.pedra well, if there are no errors it's not good. My main point is that the user tries to add empty qty and needs to get an error on the specific field he tried to add. So it goes to the log, but no error is displayed on the view to the user.

rodrigo.pedra's avatar

@SigalZ well I meant no errors when using an numeric value...

but you are right, validation is not triggered upon wire:model update.

I missed adding real-time validation:

<?php

namespace App\Http\Livewire;

use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Component;

class Sample extends Component
{
    public $qty = [];

    public Collection $models;

    protected $rules = [
        'qty' => 'array',
        'qty.*' => ['required', 'integer'],
    ];

    protected $messages = [
        'qty.*.integer' => 'Please enter the quantity',
    ];

    public function mount()
    {
        $this->models = User::query()
            ->take(10)
            ->get();
    }
    
    /////////////////////////////////////////////////////////////////////
    // ADDED THIS
    /////////////////////////////////////////////////////////////////////
    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }

    public function addSpare($spareId)
    {
        $this->validate();

        logger()->info('add spare', ['qty' => $this->qty[$spareId] ?? 0]);
        // return Redirect::back();
    }

    public function render()
    {
        return view('livewire.sample');
    }
}

Now you will get validation upon input blur, and the info() will only write to the log if the input is validated.

Reference: https://laravel-livewire.com/docs/2.x/input-validation#real-time-validation

1 like
SigalZ's avatar

@rodrigo.pedra Thank you but I'm not sure you are getting what I'm struggling with.

I don't need a real time validation.

There is a table, with an input field called 'qty' and a button called 'Add' in each row.

When the user clicks the button, it calls a function on the component that validates the field and returns an error if the field is empty or a string.

The problem is, all the fields in the table display the error because they are all called 'qty'.

With the solution you provided, I didn't get any errors at all when I should have.

Even if I needed a real time validation I would get the same issue, all the fields would get the error.

Thank you for trying.

SigalZ's avatar
SigalZ
OP
Best Answer
Level 5

OMG! I found it!!! After reading this post:

https://github.com/livewire/livewire/issues/1556

I managed to fix it.

In the component:

protected $rules = [
        'qtys.*.qty' => 'required|numeric',
 ];

protected $messages = [
        'qtys.*.qty.required' => 'The Quantity field is required',
        'qtys.*.qty.numeric' => 'The Quantity field must be a number'
];

public function mount($job)
{
        $this->job = $job;

        $jobId = $this->job->id;
        $spares = Spare::whereNotExists(function($query) use($jobId)
        {
                    $query->select(DB::raw(1))
                        ->from('job_spare')
                        ->whereRaw('spares.id = spare_id and job_id = ' . $jobId);
           })->get();

      //Initializing all the quantities fields  
      foreach($spares as $spare) {
            $this->qtys[$spare->id] = ['qty' => ''];
       }
    }

public function addSpare($spareId)
{
	//Validating only the quantity that was posted 
    $this->validate([
            "qtys.$spareId.qty" => 'required|numeric',
     ]);

      $this->job->spares()->attach($spareId, ['qty' => $this->qtys[$spareId]['qty']]);

      return Redirect::route('jobs.edit', ['job'=>$this->job, 'selectedTab'=>'#tab2']);
 }

In the blade file:

<div class="input-group">
    <input type="text" class="form-control form-control-sm @error("qtys.$model->id.qty") is-invalid @enderror" 
     wire:model.lazy="qtys.{{$model->id}}.qty">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary btn-sm" type="button" wire:click.prevent="addSpare({{$model->id}})">Add</button>
    </div>
    <x-display_error error="qtys.{{$model->id}}.qty"/>
  </div>

Now the error displays only on the field the user tried to add.

Hallelujah!!!

Please or to participate in this conversation.