mount(Model $model) returns a 404 on custom methods

I’m working on a simple component that displays all related items on an order. I’m using a nested component. In the end I’d like to change the data in the nested component.

I have the following models:

Order - columns: id
OrderItem - columns: id, article_name, order_id

There is an $order variable available in the page controller. This is either an existing Order or an empty one, initiated with new Order(). Order has a relation set up to OrderItem's.

The $order is handed to the Livewire component:

@livewire('order.overview', ['order' => $order])

The contents of the Component’s class are:

<?php

namespace App\Http\Livewire\Order;

use App\Order;
use Livewire\Component;

class Overview extends Component
{
    public $order;
    public $items = [];

    public function addItem()
    {
    }

    public function mount(Order $order)
    {
        $this->order = $order;
        $this->items = $this->getItemsFromOrder($order);
    }

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

    public function getItemsFromOrder($order)
    {
        if (!$order || !$order->exists) {
            return [[
                'id' => 0,
                'order_id' => 0,
                'article_name' => '',
            ]];
        }

        return $order->items->toArray();
    }
}

I’ve left addItem() empty for now. I know this doesn’t do anything right now.

I’ve also made a very basic view, it lists all $items as specified in the Overview class and there is a button that’s supposed to trigger addItem().

<div>
  @foreach ($items as $item)
    @livewire('order.item', ['item' => $item], key($item['id']))
  @endforeach

  <button wire:click="addItem" class="border border-green-900 p-2 mt-4">Add row</button>
</div>

All of this works without errors if $this->order exists.

If the order doesn’t exist and I pass an empty Order model, I get a 404 when addItem is supposed to run. It returns:

No query results for model [App\Order].

I don’t get that. Why would it run a query against it? mount() should only run once unless I’m misunderstanding the lifecycle hooks. If I remove $this->order = $order from mount() there are no errors but I’d like to use the order in addItem().

I must be missing something :frowning:

Hey @ju5t,

please try to remove the type-hint from your mount hook.
My guess is, that because of the type-hint Livewire tries to resolve your order using dependency injection/route model binding.

Hi @maxeckel, thanks for the suggestion. I just tried it but I still get a 404.

Your comment got me thinking though. I need the $order variable in the component, but I don’t need to move data back and forth in a Livewire model kind of way (although that would be awesome!).

I can’t recreate the error I got while tinkering with this but you can only use specific types on public variables. I forgot about that.

The solution is to make the $order protected and change render() to include $this->order as the $order variable in the view as you would with standard Laravel.

Problem solved :slight_smile:

One thing I don’t get is that I’m not able to access $this->order from the addItem()-method. What am I missing there?

Hey @ju5t,

this is strange. I created a laravelplayground containing Eloquent and Livewire to try to simulate your issue. But in there this works properly. I can pass the Model into the Component using the mount method, use the Model within the components view (without binding) and i created a method i call from a wire:click. When i dd out the model variable within the action call, the Model is still there and hydrated.

It’s really basic, but i think it mirrors your use case. Maybe you can find a difference between this and your code, or you can point me in the right direction to help you further :wink:

I’ve made a slight adjustment so it’s like my current situation. In the component I’ve made the variable protected and I have added it to the view returned by render(). I also changed the route where it passes the $post to the view and have it pass an empty Post if it can’t find any.

If you want to replicate the 404 you should just make the $post public in the Livewire component.

It now returns null in dd() when $post is protected in the component and a 404 if it’s public.

Laravel Playground is cool. I didn’t know about it until now.

Thanks for your help!

I thought I made the change but it turns out I didn’t. I’m not very familiar with Laravel Playground :slight_smile:

I’ve changed the route to:

$post = Post::find(12) ?: new Post();

This returns a 404 when you press the button.

If you make it protected in the Livewire component and pass $this->post to the view in render() it will return null.

Hey @ju5t,

I‘ll have a look at it tomorrow. I hope we can solve your problem. Maybe we can even have a closer look at it through discord if you want :wink:.

Have a nice evening!

Hey @ju5t,

i’ve recreated this locally, so that i can see more of what is going on behind the scenes.
The problem is, that when the variable is public and you store an empty model in it, Livewire will serialize the model in order to be able to restore it in the next request. But since the model is empty, eloquent can’t find it, so the 404 is thrown.

When you make the variable protected, Livewire cannot/will not store the state between requests. From the docs:

protected and private properties DO NOT persist between Livewire updates. In general, you should avoid using them for storing state.

So that’s why your variable is “null” on subsequent requests.

What i’m personally doing for now is using an array for when i need to create a new entity.

I guess you simply tested this, because from what you wrote there should be no need to pass an empty/new Model to the component. As your component is an Order overview, it should only display available orders or am i wrong?

Hi @maxeckel, thanks for the thorough debugging. I’ve never seen such a helpful community before.

I have a Vue component gets an order and lists all articles on it. You can add new articles to it dynamically and it works for both new and existing orders.

I’m trying to recreate this functionality with Livewire. I’m new to Livewire, I think I missed the part in the docs about state not being persisted for private and protected properties. And as I’m just ‘playing’ I didn’t pay much attention to the naming of things. Sorry for the confusion.

I can pass the $order->id to mount() too, but that would do another query which I wanted to prevent. I think I have not other choice, as this way I can store the id in a public property and have it assigned to articles on the order (there has to be a relation for existing orders).

It’s kind of similar to what I did with getItemsFromOrder method.

Is this a recommended solution?