skip to Main Content

I’ve set up a modal for creating a new House. Within this modal is a section for editing Rooms.

What I want to do is create a way for users to change details (name, description) of each of the rooms without anything getting written to the database until the save button for the whole modal is clicked.

views/livewire/houses/createHouse.blade.php

<div role="dialog" aria-modal="true" aria-labelledby="modal-headline">
            <form>
                <div class="w-1/2 pr-4">
                        <div class="mb-4">
                            <label for="input1">Address1:</label>
                            <input type="text" id="input1" placeholder="Enter Address" wire:model.defer="address1">
                        </div>
                        <div class="mb-4">
                            <label for="input2">Town:</label>
                            <input type="text" id="input2" placeholder="Enter Town" wire:model.defer="town">
                        </div>
                    </div>
                    <div class="w-1/2">
                        <h3 class="mb-2">Rooms</h3>
                        @foreach ($rooms as $key => $room)
                            @livewire('room-show', [$room])
                        @endforeach
                    
                    <span>
                        <button wire:click.prevent="store()" type="button">
                        Save
                        </button>
                    </span>
                </div>
            </form>
        </div>

views/livewire/rooms/room.blade.php

 <div>
        <input type="text" wire:keydown="$emitUp('roomUpdated', '{{ $room->id }}', 'name', $event.target.value"
            id="roomName" wire:model.defer="name" placeholder="Name">
<input type="text" wire:keydown="$emitUp('roomUpdated', '{{ $room->id }}', 'description', $event.target.value" id="roomDesc" wire:model.defer="name" placeholder="Description">
    </div>

http/Livewire/Houses.php

<?php

namespace AppHttpLivewire;

use AppModelsHouse;
use AppModelsRoom;
use LivewireComponent;

class Houses extends Component
{
    public $houses, $address1, $town;
    public $isOpen = 0;
    protected $listeners = ['roomUpdated' => 'updateRoom'];
    
    public function render()
    {
        $this->houses = House::all();
        return view('livewire.houses.houses');
    }

   
    public function updateRoom($id, $field, $value)
    {
        //Update the name and description for the room
        foreach ($this->rooms as $key=>$room) {
          if ($room->id == $id){
            $this->rooms[$key][$field] = $value;
            break;
          }
        }
    }

public function store()
{
    // dd($this->rooms); //--- THIS DOESN'T SHOW THE UPDATED NAME & DESCRIPTION ---//

    //Update the house details
    House::updateOrCreate(['id' => $this->house_id], [
        'address1' => $this->address1,
        'town' => $this->town
    ]);

    //delete all rooms for this house
    Room::where('house_id',$this->house_id)->delete();

    //re-add all rooms for this house
    foreach($this->rooms as $room){
        Room::updateOrCreate(['id' => $room->id], [
            'house_id' => $this->house_id,
            'name' => $room->name,
        ]); 
    }

    session()->flash('message', $this->house_id ? 'House Updated Successfully.' : 'House Created Successfully.');
}

Can anyone suggest a way for me to use $emitUp on each of the room fields so that it updates a variable without updating the database?

EDIT: I’ve now updated to Livewire v3. But I still don’t know how to solve this.

2

Answers


  1. You say you are unable to access the input values (room name and description) from within the parent House store() method.

    In your current code, the input values are stored in each Room Livewire component, which emits an event every time there is a change (via keydown). The parent Houses component listens to these events with the updateRoom method. However, in the updateRoom method, the changes are not being saved anywhere persistently.

    I suppose it is the main reason you are not seeing the updated values in the store() method: they are being lost after the updateRoom method finishes running.

    A possible solution would be to be stored persistently in the Houses component, so they can be accessed later in the store() method.
    That means initializing a $rooms property in the Houses component and updating this property in the updateRoom method.

    With these modifications, whenever a room’s details are updated and the ‘roomUpdated‘ event is emitted, the parent component’s $rooms property gets updated. Then, when the store() method is called, the $rooms property contains all the updated room details, so you can access the input values in store().

    Your views/livewire/houses/createHouse.blade.php remains unchanged.

    In views/livewire/rooms/room.blade.php, emit an event when the input field loses focus (i.e., when you click outside of it).

    <div>
        <input type="text" wire:blur="$dispatch('roomUpdated', ['id' => $room->id, 'field' => 'name', 'value' => $event.target.value])" 
            id="roomName" wire:model.defer="name" placeholder="Name">
        <input type="text" wire:blur="$dispatch('roomUpdated', ['id' => $room->id, 'field' => 'description', 'value' => $event.target.value])" 
            id="roomDesc" wire:model.defer="description" placeholder="Description">
    </div>
    

    The wire:blur event listener is used to trigger the dispatch of the roomUpdated event when a user finishes editing a room’s details (i.e., when the input field loses focus). This is done using the Livewire V3 $dispatch method, which replaces the old emit() or dispatchBrowserEvent.
    That way, the Houses parent component can catch this event, denoted by the #[On('roomUpdated')] attribute, and update its $rooms property accordingly. The event name and relevant parameters are passed with $dispatch. This communication allows the state of the room’s details to be tracked without saving them to the database until the final confirmation from the user.

    wire:keydown (as in your original code) may cause the event to be emitted too frequently, which could affect performance. Emitting the event on wire:blur instead ensures the event is only emitted when the user finishes their edits. That is generally more efficient and gives the desired result in your case.

    That assumes that each room component has access to the $key of the room, which is its index in the $rooms array in the parent Houses component.

    Without this change, the parent component would not receive the updated room details, and thus would not be able to store them for later access in the store() method.

    But in http/Livewire/Houses.php, do initialize the $rooms property, listen to the roomUpdated event, and update the $rooms array whenever that event is triggered.

    <?php
    
    namespace AppHttpLivewire;
    
    use AppModelsHouse;
    use AppModelsRoom;
    use LivewireComponent;
    use LivewireOn;
    
    class Houses extends Component
    {
        public $houses, $address1, $town, $rooms;
        public $isOpen = 0;
    
        public function render()
        {
            $this->houses = House::all();
    
            if($this->house_id){
                $house = House::find($this->house_id);
                $this->rooms = $house->rooms->toArray();  // fetch related rooms
            } else {
                $this->rooms = [];  // for a new house, start with an empty list of rooms
            }
    
            return view('livewire.houses.houses');
        }
    
        #[On('roomUpdated')]
        public function updateRoom($params)
        {
            //Update the name and description for the room
            $this->rooms[$params['id']][$params['field']] = $params['value'];
        }
    
        public function store()
        {
            // dd($this->rooms); //--- THAT SHOULD NOW SHOW THE UPDATED NAME & DESCRIPTION ---//
    
            // Update the house details
            House::updateOrCreate(['id' => $this->house_id], [
                'address1' => $this->address1,
                'town' => $this->town
            ]);
    
            // Delete all rooms for this house
            Room::where('house_id',$this->house_id)->delete();
    
            // Re-add all rooms for this house
            foreach($this->rooms as $room){
                Room::updateOrCreate(['id' => $room->id], [
                    'house_id' => $this->house_id,
                    'name' => $room['name'],
                    'description' => $room['description'],
                ]); 
            }
    
            session()->flash('message', $this->house_id ? 'House Updated Successfully.' : 'House Created Successfully.');
        }
    }
    

    In the updateRoom function, the $rooms array is being directly manipulated using $this->rooms[$params['id']][$params['field']] = $params['value']; line. This line of code directly changes the specific room’s field (either ‘name‘ or ‘description‘) in the $rooms array held by the Houses component.

    Login or Signup to reply.
  2. You can do something like this

    public $roomsData = [];
    
    public function updateRoom($id, $field, $value)
    {
        if (!array_key_exists($id, $this->roomsData)) {
            // If not, create a new one with default values
            $this->roomsData[$id] = [
                'name' => '',
                'description' => '',
            ];
        }
    
        // Update
        $this->roomsData[$id][$field] = $value;
    }
    

    the in store() method

    foreach($this->rooms as $room){
        if (array_key_exists($room->id, $this->roomsData)) {
            $changes = $this->roomsData[$room->id];
            $room->name = $changes['name'];
            $room->description = $changes['description'];
        }
        
        Room::updateOrCreate(['id' => $room->id], [
            'house_id' => $this->house_id,
            'name' => $room->name,
            'description' => $room->description,
        ]); 
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search