4

There are 3 livewire components UserIsExpired, UserIsActive and UserIsPending and 3 buttons respective to each component. When a button is clicked, it should replace previous component with its respective component.

<button wire:click="$emit(active)">{{ __('Active') }}</button>
<button wire:click="$emit(pending)">{{ __('Pending') }}</button>
<button wire:click="$emit(expired)">{{ __('Expired') }}</button>

In view

<livewire:user-is-active :active="$active"/>
<livewire:user-is-pending :pending="$pending"/>
<livewire:user-is-expired :expired="$expired"/>

Component example

class UserIsExpired extends Component
{
    protected $listeners = ['expired'];    
    public function render()
    {
        return <<<'blade'
            <div>
                {{-- The best athlete wants his opponent at his best. --}}
            </div>
        blade;
    }
}

When Active button is clicked, it should load UserIsActive component. Same goes for other two.

I have been looking livewire doc for long, but unable to find how to make it happen. Thanks in advance.

Ahsan
  • 1,289
  • 3
  • 13
  • 37

3 Answers3

8

I would wrap your components in a component container and use it to manage the visibility of your other components.

component-contaoner.blade.php

<div>
    <h4>Component Container</h4>

    {{-- this is your dynamic component --}}
    @livewire($component, key($key))

    <button wire:click="$emit('switch', 'active')">
        {{ __('Active') }}
    </button>

    <button wire:click="$emit('switch', 'pending')">
        {{ __('Pending') }}
    </button>

    <button wire:click="$emit('switch', 'expired')">
        {{ __('Expired') }}
    </button>
</div>

ComponentContainer.php

class ComponentContainer extends Component
{
    private $component = '';

    protected $listeners = [
        'switch'
    ];

    public function switch(string $component)
    {
        $this->component = $component;
    }

    public function mount(string $component = 'active')
    {
        $this->component = $component;
    }

    public function render()
    {
        return view('livewire.component-container', [
            'component' => $this->component,
            // key is required to force a refresh of the container component
            'key' => random_int(-999, 999),
        ]);
    }
}

Then you would use the component container as follows, passing in an optional component to show initially otherwise it defaults to active as set in the mount function.

@livewire('component-container')

You could put the buttons anywhere you want and use

$emitTo('container-component', 'switch', 'active')

I just put them inside the component-container for ease.

A nice thing about this approach is there are no if conditionals to manage and should you add more components to switch between, all you need to do is add another button somewhere with the relevant $emit.

Peppermintology
  • 9,343
  • 3
  • 27
  • 51
  • Just came across this and it will be incredibly useful for something I'm trying to do. One minor nitpick: the random range for the key seems small to consistently guarantee a refresh. I assume it's just an illustrative number choice, but maybe something like random_int(PHP_INT_MIN, PHP_INT_MAX) will reduce issues for people that copy/paste without thinking about it. Maybe even using time (microseconds since epoch) could be more reliable than a random number. – anjama Feb 16 '22 at 17:52
  • @anjama The values were purely illustrational, feel free to adapt and use whatever mechanism is appropriate for your use case. Glad it could be of use to you though. – Peppermintology Feb 17 '22 at 09:22
  • You can do without passing 'component' in the view if you set the `$component` property to `public`'. – asheroto Oct 02 '22 at 12:07
  • This method somehow seems to trigger the "Multiple root elements detected." error in the console even though each component only has one element. Do you have any idea what could be causing this? – Joseph Jan 13 '23 at 16:30
  • @Joseph Difficult to say without seeing your code. Perhaps [ask a question](https://stackoverflow.com/questions/ask)? – Peppermintology Jan 15 '23 at 08:22
  • @Peppermintology I was referring to the code provided in this answer. Sorry if that wasn't clear. I did ask a [question](https://stackoverflow.com/questions/75112448) but didn't get any responses. However, I've since figured out the issue and answered my own question there. – Joseph Jan 16 '23 at 09:09
  • That's clever :) It can also be triggered by javascript too. – oceceli Aug 20 '23 at 13:59
0

One way to solve this is be adding all listeners to each of the components and switch a flag. Here on the example of UserIsExpired:

class UserIsExpired extends Component
{
    public $state = 'invisible';

    protected $listeners = [
        'active',
        'pending',
        'expired',
    ];

    public function active()
    {
        $this->state = 'invisible';
    }

    public function pending()
    {
        $this->state = 'invisible';
    }

    public function expired()
    {
        $this->state = 'visible';
    }

    public function render()
    {
        if ($this->state === 'invisble') {
            return '';
        }

        return <<<'blade'
            <div>
                {{-- The best athlete wants his opponent at his best. --}}
            </div>
        blade;
    }
}

For the initially visible component you probably want to set the default value for the state to visible.

spekulatius
  • 1,434
  • 14
  • 24
0

Another way you can take is setting a self property and in blade make the corresponding livewire directive like if/else (assuming all is under full page component and the model binding refer a user model)

<button wire:click="showNested('active')">{{ __('Active') }}</button>
<button wire:click="showNested('pending')">{{ __('Pending') }}</button>
<button wire:click="showNested('expired')">{{ __('Expired') }}</button>

In view

@if($show == 'isActive')
   <livewire:user-is-active :active="$active"/>
@elseif($show == 'isPending')
   <livewire:user-is-pending :pending="$pending"/>
@elseif($show == 'isExpired')
   <livewire:user-is-expired :expired="$expired"/>
@endif

in component

public $active,$pending,$expired;
public $show = '';

public function render()
{
   if(!empty($this->show)){
      if($this->show == 'active')
         $this->active = User::where('status','active')->first();
      elseif(....)
        //......
   }
   return view(.....) //......;  
}

public function showNested($value)
{
   $this->show = $value;
}

Prospero
  • 2,190
  • 2
  • 4
  • 18