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

morceaudebois's avatar

I don't understand Livewire's new way to use Eloquent collection with forms

Hi! I updated my project from Livewire 2 to 3 a few days ago and I'm struggling to understand the new way you're supposed to manage the binding of Eloquent collections to form inputs.

For context, I have a form on a settings page that I want to populate with some data from an Eloquent collection. I've been doing it this way in the component:

public function mount() {
     $this->plans = $this->form->plans()->with('tables')->get();
}

Then in the view:

<x-all.text-input wire:model="plans.{{ $key }}.title" type="text" placeholder="Titre du plan" />

The documentation states that "In Livewire 3, binding directly to Eloquent models has been disabled in favor of using individual properties, or extracting Form Objects". But as there are no examples, what does it mean exactly? Because the Form Objects solution seems overkill for my situation, so I'm not sure I want to go this route.

I thought of transforming the collection into a regular array, but for some reason this seems a bit hacky and not really the way to go. Am I missing something?

Appreciate the help ✨

0 likes
2 replies
morceaudebois's avatar

I just spent a few more hours on this and I still have no idea what to do. I tried setting up a Form Object but don't understand how that's considered a replacement for binding to Eloquent collections, as it's just moving the problem somewhere else while adding extra steps.

Transforming the collection into an array could work but it brings in its share of problems and makes my code way more complicated and less readable, so I'm still not feeling like it's the proper solution.

I'm surprised I can't find any more info on this, but I guess I'll just use the legacy_model_binding config settings for now, even if that's less than ideal. Would love some insight, thank you!

Don't know if it can help but for more context, here's what the view looks like and my component in its original state:

class Plans extends Component {
    public $form, $plans;
    public $selectedTables = [];
    public $plansToDelete = [];

    protected $rules = [
        'plans.*.title'=> 'required|string|min:3',
        'plans.*.order'=> 'required|int',
        'plans.*.form_id'=> 'required|int' 
    ];

    public function mount() {
        // Load the plans with their related tables
        $this->plans = $this->form->plans()->with('tables')->get();

        // Initialize selectedTables array
        foreach ($this->plans as $key=>$plan) {
            $this->selectedTables[$key] = $plan->tables->pluck('id')->toArray();
        }
    }

    public function addPlan() {
        $last = $this->plans->keys()->last() ?? 0;

        // Add the new plan to the collection
        $this->plans->add(new Plan([
            'order' => $last++,
            'form_id' => $this->form->id
        ]));
        
        $this->selectedTables[$last] = [];
    }

    public function deletePlan($planId, $key) {
        // if plan in the database, stores its id to delete it later
        if ($this->plans->get('id', $planId)) {
            $this->plansToDelete[] = $planId;
        }

        // removes the plan from the collection locally
        unset($this->plans[$key]);

        // removes the selected tables for this plan 
        unset($this->selectedTables[$key]);

        // then reindexes the array 
        $this->selectedTables = array_values($this->selectedTables);
    }

    public function submit() {
        $this->validate();

        // if plans to be deleted
        if (!empty($this->plansToDelete)) {
            \App\Models\Plan::whereIn('id', $this->plansToDelete)->delete();

            // Clear the array
            $this->plansToDelete = [];
        }

        foreach ($this->plans as $key=>$plan) {
            $plan->save(); // saving the title

            if (isset($this->selectedTables[$key])) {
                // Sync the tables for the plan
                $plan->tables()->sync($this->selectedTables[$key]);
            }
        }
    }
    
    public function render() {
        return view('livewire.options-form.plans');
    }
}
shyberry's avatar

Your case looks like what I encountered recently. In your view, you try to add a value attribute:

<x-all.text-input wire:model="plans.{{$key}}.title" type="text" placeholder="Titre du plan"  value="{{$plans[$key]->title}}" />

Please or to participate in this conversation.