skip to Main Content

Blade for livewire component:

<div>
    <input type="checkbox" class="form-checkbox" wire:model="collapsed" wire:click="toggleCollapsed">
    @if ($collapsed)
        show icon for collapsed state
    @else
        show icon for expanded state
    @endif
</div>

Works as expected – $collapsed changed because of checkbox, appropriate icon shown.

But usage of checkbox for toggle visual state is unlogical.

Try to use more logical button:

<div>
    <button wire:model="collapsed" wire:click="toggleCollapsed">
        @if ($collapsed)
            show icon for collapsed state
        @else
            show icon for expanded state
        @endif
    </button>
</div>

Don’t work.

First thought – button is not toggler itself, so I should inform, that collapse state changed:

public function toggleCollapsed(): void
{
    $this->collapsed = !$this->collapsed;
}

Don’t work again.

Moreover this->collapsed every time I entered into toggleCollapsed() has initial value – handy toggle just ignored.

Guess I should inform component after toggling $this->collapsed = !$this->collapsed that component state changed (I see it is not the same as component’s public bool $collapsed property). How to do that?

P.S. I’m strongly against of usage wire:click="$toggle('collapsed')" because toggleCollapsed() contains more complex logic not shown here.

2

Answers


  1. The issue you’re facing is due to the fact that the wire:model directive is primarily used for input fields (like checkboxes) and it’s not applicable to elements like buttons. That’s why the checkbox works but the button doesn’t toggle the collapsed state properly.

    Here’s a step-by-step guide on how to handle the toggle using the button without the need for wire:model:

    1. Remove wire:model from the button
      Since wire:model is not suitable for buttons, remove it from the button. The button’s role here is just to trigger the toggle, and wire:click will handle that.

    2. Update the Blade Template
      Your updated Blade template should look like this:

        <div>
            <button wire:click="toggleCollapsed">
                @if ($collapsed)
                    <span>Collapsed Icon</span>
                @else
                    <span>Expanded Icon</span>
                @endif
            </button>
        </div>
    
    1. Ensure the toggleCollapsed() Method Works
      The method you already wrote for toggling the collapsed state is correct:
        public function toggleCollapsed(): void
        {
            $this->collapsed = !$this->collapsed;
        }
    

    This method should be fine as long as $collapsed is a public property of your Livewire component.

    1. Check if the collapsed Property is Declared Correctly
      Ensure that the collapsed property is declared as a public property in your Livewire component:
        class YourComponent extends Component
        {
            public bool $collapsed = false;
        
            public function toggleCollapsed(): void
            {
                $this->collapsed = !$this->collapsed;
            }
        
            public function render()
            {
                return view('livewire.your-component');
            }
        }
    
    1. Why wire:model Doesn’t Work with Buttons
      The wire:model directive is used to bind properties to form inputs (e.g., text fields, checkboxes) to synchronize the UI with the backend state in real-time. Since a button doesn’t have a "value" or a state like a checkbox, wire:model is ineffective here.

    By removing wire:model and relying on wire:click to manually toggle the $collapsed state, your button will work as expected. This method gives you the control you need to handle more complex logic inside the toggleCollapsed() function.

    Login or Signup to reply.
  2. Livewire 3 comes bundled with AlpineJs which is more useful in this case as the state of the menu can be entirely controlled client-side and then entangled into a Livewire property like the following example from the Livewire Docs:

    <div x-data="{ open: $wire.entangle('showDropdown') }">
        <button x-on:click="open = true">Show More...</button>
     
        <ul x-show="open" x-on:click.outside="open = false">
            <li><button wire:click="archive">Archive</button></li>
     
            <li><button wire:click="delete">Delete</button></li>
        </ul>
    </div>
    

    As we can see in this snippet we can define the state of the menu, open in an outer div, toggling state on the click of the button and closing when clicking away.

    In the above example we entangle the state of the client side open with the Livewire property showDropdown, or in your case $collapsed. We can then hook into the property update and execute any other logic around this.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search