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
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 parentHouses
component listens to these events with theupdateRoom
method. However, in theupdateRoom
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 theupdateRoom
method finishes running.A possible solution would be to be stored persistently in the
Houses
component, so they can be accessed later in thestore()
method.That means initializing a
$rooms
property in theHouses
component and updating this property in theupdateRoom
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 thestore()
method is called, the$rooms
property contains all the updated room details, so you can access the input values instore()
.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).The
wire:blur
event listener is used to trigger the dispatch of theroomUpdated
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 oldemit()
ordispatchBrowserEvent
.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 onwire: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 parentHouses
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 theroomUpdated
event, and update the$rooms
array whenever that event is triggered.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 theHouses
component.You can do something like this
the in
store()
method