Search code examples
phplaravellaravel-livewire

Combining Multiple Laravel Livewire "updated" Lifecycle Hooks Into a Single Function


I need to update the price on the page when a user changes the value of either the width, height and/or length. The code below works, but it seems it could be cleaner instead of having three separate updated lifecycle hooks with duplicate code. When the view is mounted, I set the default values of width, height and length and calculate the price.

show.php

...
class Show extends Component
{
    public $product;
    public $sku;
    public $name;
    public $description;
    public $width;
    public $height;
    public $length;
    public $price;

    public function mount(Product $product){
        $this->product = $product;
        $this->sku = $product->sku;
        $this->name = $product->name;
        $this->description = $product->description;

        $this->width = 4;
        $this->height = 4;
        $this->length = 10;
        $this->price = (((($this->width + $this->height + $this->height)*12)/144)*25)*$this->length;
    }

    public function updatedWidth() {
        $width = $this->width;
        $height = $this->height;
        $length = $this->length;
        $this->price = (((($width + $height + $height)*12)/144)*25)*$length;
    }

    public function updatedHeight() {
        $width = $this->width;
        $height = $this->height;
        $length = $this->length;
        $this->price = (((($width + $height + $height)*12)/144)*25)*$length;
    }

    public function updatedLength() {
        $width = $this->width;
        $height = $this->height;
        $length = $this->length;
        $this->price = (((($width + $height + $height)*12)/144)*25)*$length;
    }

    public function render()
    {
        return view('livewire.shop.show')->layout('layouts.frontend');
    }
}

show.blade.php

<!-- ... -->
<div class="mt-10 px-4 sm:px-0 sm:mt-16 lg:mt-0">
    <h1 class="text-3xl font-extrabold tracking-tight text-gray-900">{{$product->name}}</h1>

    <div class="mt-3">
        <h2 class="sr-only">Product information</h2>
        <p wire:model="price" class="text-3xl text-gray-900">${{number_format($price, 2, '.')}}</p>
    </div>

    <div class="mt-6">
        <h3 class="sr-only">Description</h3>

        <div class="text-base text-gray-700 space-y-6">
            <p>
                {{$product->description}}
            </p>
        </div>
    </div>

    <form wire:submit.prevent="createOrderItem" method="POST" class="mt-6">
        <div>
            <h3 class="text-sm text-gray-600">Size</h3>

            <div class="grid grid-cols-4 gap-4">
                <div class="mt-4 sm:mt-0 sm:pr-9">
                    <label for="width" class="block text-sm font-medium text-gray-700">Outer Width:</label>
                    <select wire:model="width" id="width" name="width" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
                        <option value="3">3"</option>
                        <option value="4">4"</option>
                        <option value="5">5"</option>
                    </select>
                </div>

                <div class="mt-4 sm:mt-0 sm:pr-9">
                    <label for="height" class="block text-sm font-medium text-gray-700">Outer Height:</label>
                    <select wire:model="height" id="height" name="height" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
                        <option value="3">3"</option>
                        <option value="4">4"</option>
                        <option value="5">5"</option>
                    </select>
                </div>

                <div class="mt-4 sm:mt-0 sm:pr-9">
                    <label for="length" class="block text-sm font-medium text-gray-700">Length:</label>
                    <select wire:model="length" id="length" name="length" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
                        <option value="10">10'</option>
                        <option value="11">11'</option>
                        <option value="12">12'</option>
                    </select>
                </div>
            </div>
        </div>
<!-- ... -->

Solution

  • Since you are doing the same calculation multiple times, it would make sense to extract that into its own method. Then, you can use the "global" updated() lifecycle-hook and conditionally set the price based on which fields were updated.

    class Show extends Component
    {
        public $product;
        public $sku;
        public $name;
        public $description;
        public $width;
        public $height;
        public $length;
        public $price;
    
        public function mount(Product $product) 
        {
            $this->product = $product;
            $this->sku = $product->sku;
            $this->name = $product->name;
            $this->description = $product->description;
    
            $this->width = 4;
            $this->height = 4;
            $this->length = 10;
            $this->price = $this->calculatePrice();
        }
    
        /**
         * Calculates the price based on height, width and length 
         * @return int $calculatedPrice
         */
        private function calculatePrice() 
        {
            return (((($this->width + (2 * $this->height)) * 12) / 144) * 25) * $this->length;
        }
    
        /**
         * Life-cycle hook that will fire on each updated value from the view
         * @param string $field The fieldname that is updated
         */
        public function updated($field) 
        {
            if (in_array($field, ['width', 'height', 'length'])) {
                $this->price = $this->calculatePrice();
            }
        }
    
        public function render()
        {
            return view('livewire.shop.show')
                    ->layout('layouts.frontend');
        }
    }