How to store models' info, and how to check if model is dirty

Hello, everyone

I’m having a hard time both understanding and finding clear, unambiguous information on how to represent a model with Livewire.

I want to render multiple Eloquent models with 2 text fields (‘l’ and ‘r’). Each text field would be a text area editable through tinyMCE. If any of the fields have changes, a “save changes” button should be displayed. I’m honestly having too much trouble trying to figure out how this should be done.

  1. Should I save the Eloquent model itself as a variable ( public $model; ) or the fields ( public $l; public $r; ) inside the Livewire class?

  2. Let’s ignore tinyMCE for a minute and assume ‘l’ and ‘r’ are basic text areas. How should wire:dirty be used?
    2.1) In case the eloquent model is stored as a variable, should I use wire:target="model"? Or should I point wire:target to the attribute(s) I’d like to check for changes?
    2.2) In case each attribute is stored as its own variable, how can I check if any of the variables is dirty? wire:target="l" wire:target="r"?

I’m sorry if these are basic questions that have been answered somewhere; I’ve spent a few hours trying out most possibilities and reading the docs and any tutorials I could find (hardly any of them decent), and I just can’t wrap my head around what Livewire’s best practices are supposed to be.

For learning livewire, I recommend subbing for the video series on this site. It’s very comprehensive and shows a lot of tips, tricks, and processes that aren’t covered in the docs. It also has a bunch of real-world examples like integrating a wysiwyg.

Break it down and get a bit more simplistic with livewire until you understand how it operates then work on bolting the niceties like a text editor. This will be a little more complex and will requiring hooking into livewire in ways that don’t seem apparent.

For example, it’s common to have your wrapping div as the model so that all javascript events below bubble up and are propagated to through to the backend. An example would look like:

component.blade.php

<div> {{-- livewire root --}}
    <div wire:model="textBody">
        <input wire:ignore type="text" id="tinyMCE">
    </div> 
</div>

In this example, the model textBody is taking the events fired from the input field as they are bubbled up. Adding wire:ignore to the input prevents livewire from updating that field so that your tinyMCE instance remains the same.

To answer your questions:

  1. Saving to the model is the straight forward way to use livewire. Adding wire:model="var" is the standard way to do it where var is a public variable in your livewire component code.
  2. wire:dirty is a flag that shows a field has changed and needs to be passed through to the backend. In practice, I rarely if ever use this as model sync is nearly instantaneous. It’s helpful for things like wire:model.lazy where there’s a significant or undetermined time that passes or requires user engagement to update the model. Again, this is rare as the standard usage of wire:model updates very quickly. I wouldn’t overly concern myself with wire:dirty while trying to get a basic grasp of livewire.

Strip down your app to a playground and get comfortable with how livewire works before trying to jump in a lot of other things. Set up things like a simple form with a simple text input and play around until you get an understanding of livewire. Something simple like:

Component.php

public $var
component.blade.php

<div>
    {{ $var }}
    <input wire:model="var">
</div>

can show you how powerful and easy livewire can be. A lot of common pitfalls occur because there’s a lack of understanding of where things lie in the javascript world and where they lie in the php world.

If you’re brand new, the first series of the screencast walks you through the basics of livewire. Again, I recommend watching it: https://laravel-livewire.com/screencasts

@shortbrownman Thank you for your reply!

I’ve watched a few of the videos (I’ve become a sponsor), but I haven’t had enough time to watch them patiently and having searchable documentation is quite important for me. I can’t even watch videos at work, which is when I’m most likely to need the information…

In this particular use case, I’d like the changes to only be updated through user action (therefore wire:model.lazy is what I’d like to use), and I’d like the user to be aware of the fact that there are unsaved changes (that’s why I wanted to use wire:dirty ).

My currently working view is as follows (all unrelated code removed):

<form wire:submit.prevent="submit(Object.fromEntries(new FormData($event.target)))">
    <div wire:ignore>
        <textarea wire:model.lazy="l" name="l">{!! $l !!}</textarea>
        <textarea wire:model.lazy="r" name="r">{!! $r !!}</textarea>
    </div>
    <button type="submit">Save</button>
</form>

And the class, simplified for clarity:

class ClassType extends Component
{
    public $modelId;
    public $r;
    public $l;


    public function mount($modelId)
    {
        $this->modelId = $modelId;
    }

    public function render()
    {
        $model = Model::find($this->modelId);

        $this->l = $model->l;
        $this->r = $model->r;

        return view('livewire.curriculum.class-type');
    }

    public function submit($input)
    {
        Model::query()
            ->where('id', $this->modelId)
            ->update([
                'l' => $input['l'] ?? null,
                'r' => $input['r'] ?? null
            ]);

        $this->l = $input['l'];
        $this->r = $input['r'];

        session()->flash('success', 'Updated!');
    }
}

Since I’m having tinyMCE save the input to the textareas whenever the contest changes (on input), this is working decently now. I’m just missing the “dirty” behavior (for example, disabling or hiding the “submit” button unless the content has changed). Is there an easy enough way to make it work in a situation like this?

ETA: I’m updating the model through update on the query builder, rather than $model->update() because it has a composite primary key, and in my experience Laravel doesn’t play well with those, unfortunately. And I think I’ve had errors on Livewire because of that as well.

Signing up to sponsor livewire will be worth it for just the vids alone. Plus, you get to support the developer and hopefully grow the project. Awesome!

For wire:dirty and the submit button, you can add a class to hide on dirty state. That might look like this:

<button wire:dirty.class="hidden" wire:target="l">Submit</button>

This example will monitor the dirty state of the model l and will apply the class hidden if the model is dirty.

That’s exactly what I’ve tried to do before, but a) it’s not working, and b) I’d like it to check if either “l” or “r” are “dirty”.

Just to make sure, this is the code I tried, with unnecessary data removed:

<div class="d-none" wire:dirty.class.remove="d-none" wire:target="l">
    <button type="submit">Save</button>
</div>

(d-none is a BS4 class that sets display: none;, so I want it to be removed if “l” or “r” are dirty)

If you setup a simple example, you can see the wire:dirty behavior:

component.blade.php

<div> {{ --livewire root --}}
    <div>
        {{ $l }} <!-- let's output to verify l stays the same -->
        <input wire:model="l">
        <div wire:target="l" wire:dirty.class.remove="hidden" class="hidden">dirty</div>
    </div>
</div>

This works for me. hidden is a tailwind class that’s like bootstrap’s d-none. So what might be happening is that the “dirty” state isn’t being updated. It’s likely that the javascript within your model isn’t bubbling up or is bubbling up a different event. I think there’s a video in the screencasts that covers this.

Basically, you need to find whatever event is being raised by tinyMCE that represents the “change” and forward that event as a regular javascript onchange event. This will bubble up to your model and be flagged as changed but since the .lazy modifier is attached, the model won’t update and will remain in the dirty state.

Awesome, this is a good solution