Search code examples
phplaravellaravel-livewire

How to pass state from mount() to render() in Livewire?


In my Livewire component I have these methods (somewhat simplified for the purposes of the question):

class Form extends Component
{ 
    protected mixed $page;

    public function mount($pageId): void
    {
        // Get the page type here, which will determine the UI strings
        $this->page = DB::table('pages')->find($pageId);
    }

    public function render(): View
    {
        return view(
            'livewire.titlegen.form',
            [
                'page' => $this->page,
            ]
        );
    }
}

I render it from this Blade template:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
            {{ __('Title') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <livewire:titlegen.form :pageId="$pageId"/>
    </div>
</x-app-layout>

As you can see, the pageId in the template is passed to mount().

Now, the page initially renders fine; the component mounts, and then renders immediately. However if I call a Livewire method, then the component renders without mounting first. I get this error:

Typed property App\Livewire\Titlegen\Form::$page must not be accessed before initialization

I have tried adding $pageId to the render method, in case that's a convention, but this does not work.


Solution

  • There is a detail in the documentation that shows this is expected behaviour:

    In Livewire components, you use mount() instead of a class constructor __construct() like you may be used to. NB: mount() is only ever called when the component is first mounted and will not be called again even when the component is refreshed or rerendered.

    [ Source ]

    My solution was obvious in hindsight; the mount function should write its data to public, two-way binding parameters, which are then accessible to the render method even when mount is not called:

    class Form extends Component
    {
        // Public properties are two-way bound
        public int $pageId;
    
        public function mount($pageId): void
        {
            $this->pageId = $pageId;
        }
    
        public function render(): View
        {
            $page = DB::table('pages')->find($this->pageId);
    
            return view(
                'livewire.titlegen.form',
                [
                    'page' => $page,
                ]
            );
        }
    }
    

    In this example I've chosen to just pass the integer key, as I don't want this two-way binding channel to be passing large objects. That said, a database row object would probably be fine.