Livewire: componentes reativos sem JavaScript

Introdução ao Livewire e seu Lugar no Ecossistema PHP

Livewire é uma biblioteca full-stack para Laravel que permite criar interfaces dinâmicas e reativas sem escrever uma única linha de JavaScript. Desenvolvido por Caleb Porzio, o Livewire funciona como uma camada que conecta o frontend Blade ao backend PHP, enviando requisições AJAX automaticamente sempre que o usuário interage com um componente.

Diferente de abordagens tradicionais como jQuery (que exige manipulação manual do DOM) ou frameworks modernos como Vue e React (que demandam conhecimento separado de JavaScript), o Livewire mantém toda a lógica no PHP. Comparado ao Inertia.js, que também reduz a necessidade de JS mas ainda requer um framework frontend, o Livewire é ainda mais "PHP-centric", funcionando exclusivamente com Blade.

Pré-requisitos: Laravel 8+, Blade, conceitos básicos de componentes e rotas.

Instalação e Primeiro Componente

A instalação é feita via Composer:

composer require livewire/livewire

Em seguida, inclua os assets do Livewire no layout principal:

<html>
<head>
    @livewireStyles
</head>
<body>
    @livewireScripts
</body>
</html>

Vamos criar nosso primeiro componente: um contador reativo. Primeiro, geramos o componente com o Artisan:

php artisan make:livewire Contador

Isso cria dois arquivos:
- app/Http/Livewire/Contador.php (classe PHP)
- resources/views/livewire/contador.blade.php (view Blade)

Classe do componente:

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Contador extends Component
{
    public $count = 0;

    public function incrementar()
    {
        $this->count++;
    }

    public function decrementar()
    {
        $this->count--;
    }

    public function render()
    {
        return view('livewire.contador');
    }
}

View Blade:

<div>
    <h1>Contador: {{ $count }}</h1>
    <button wire:click="incrementar">+</button>
    <button wire:click="decrementar">-</button>
</div>

Para usar o componente em qualquer view:

<livewire:contador />

Ciclo de Vida e Reatividade

O Livewire possui métodos de ciclo de vida que permitem controlar o comportamento do componente:

  • mount() — executado uma vez na inicialização
  • hydrate() — executado em cada requisição (inclusive na primeira)
  • updating($property, $value) — antes de uma propriedade ser atualizada
  • updated($property, $value) — depois de uma propriedade ser atualizada
  • render() — sempre que o componente é renderizado
public function mount()
{
    $this->count = session('count', 0);
}

public function updating($property, $value)
{
    if ($property === 'count' && $value > 100) {
        $this->count = 100;
    }
}

A reatividade é automática: qualquer propriedade public que for alterada dispara um re-render do componente. Modificadores como #[Computed] permitem criar propriedades computadas que não são enviadas ao frontend, mas podem ser acessadas na view:

use Livewire\Attributes\Computed;

#[Computed]
public function dobro()
{
    return $this->count * 2;
}

Ações e Eventos no Frontend

As interações do usuário disparam ações no backend através de diretivas Blade:

<button wire:click="salvar">Salvar</button>
<input wire:keydown.enter="buscar">
<form wire:submit.prevent="enviar">...</form>

É possível passar parâmetros diretamente:

<button wire:click="remover({{ $item->id }})">Remover</button>

Para comunicação entre componentes, usamos eventos:

Emitindo evento:

$this->dispatch('item-atualizado', itemId: $item->id);

Escutando no frontend:

<div wire:key="item-{{ $item->id }}">
    {{-- conteúdo --}}
</div>

<script>
    document.addEventListener('livewire:init', () => {
        Livewire.on('item-atualizado', (event) => {
            alert('Item ' + event.itemId + ' atualizado!');
        });
    });
</script>

O wire:model oferece binding bidirecional para inputs:

<input wire:model.lazy="nome" type="text">

Modificadores como .lazy (atualiza apenas no blur), .debounce.500ms e .throttle controlam a frequência das requisições.

Validação e Formulários Reativos

O Livewire integra-se perfeitamente com o sistema de validação do Laravel:

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class CadastroUsuario extends Component
{
    public $nome = '';
    public $email = '';
    public $senha = '';

    protected $rules = [
        'nome' => 'required|min:3',
        'email' => 'required|email|unique:users,email',
        'senha' => 'required|min:8',
    ];

    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }

    public function salvar()
    {
        $this->validate();

        User::create([
            'nome' => $this->nome,
            'email' => $this->email,
            'senha' => bcrypt($this->senha),
        ]);

        session()->flash('message', 'Usuário cadastrado com sucesso!');
        $this->reset(['nome', 'email', 'senha']);
    }

    public function render()
    {
        return view('livewire.cadastro-usuario');
    }
}

View com feedback em tempo real:

<form wire:submit.prevent="salvar">
    <div>
        <label>Nome:</label>
        <input wire:model.lazy="nome" type="text">
        @error('nome') <span class="error">{{ $message }}</span> @enderror
    </div>

    <div>
        <label>Email:</label>
        <input wire:model.lazy="email" type="email">
        @error('email') <span class="error">{{ $message }}</span> @enderror
    </div>

    <div>
        <label>Senha:</label>
        <input wire:model.lazy="senha" type="password">
        @error('senha') <span class="error">{{ $message }}</span> @enderror
    </div>

    <button type="submit">Cadastrar</button>

    @if (session()->has('message'))
        <div class="success">{{ session('message') }}</div>
    @endif
</form>

Componentes Avançados e Boas Práticas

Componentes aninhados e comunicação

Componentes filhos podem emitir eventos para pais usando $emitUp:

// No componente filho
$this->dispatch('selecionado', id: $this->itemId)->to('pai-componente');

Ou diretamente no Blade:

<button wire:click="$emitUp('selecionado', {{ $item->id }})">Selecionar</button>

Traits para reutilização

trait ComPaginacao
{
    public $perPage = 10;
    public $page = 1;

    public function proximaPagina()
    {
        $this->page++;
    }

    public function paginaAnterior()
    {
        $this->page = max(1, $this->page - 1);
    }
}

Performance com lazy loading e polling

Para componentes que carregam dados pesados:

use Livewire\Attributes\Lazy;

#[Lazy]
public function dados()
{
    return Dado::all();
}

Polling automático para atualizações em tempo real:

<div wire:poll.5s>
    {{-- conteúdo que será atualizado a cada 5 segundos --}}
</div>

Testes e Depuração de Componentes Livewire

Testar componentes Livewire é simples com o helper Livewire::test():

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Livewire\Livewire;
use App\Http\Livewire\Contador;

class ContadorTest extends TestCase
{
    /** @test */
    public function pode_incrementar()
    {
        Livewire::test(Contador::class)
            ->assertSet('count', 0)
            ->call('incrementar')
            ->assertSet('count', 1)
            ->assertSee('Contador: 1');
    }

    /** @test */
    public function valida_campo_obrigatorio()
    {
        Livewire::test(CadastroUsuario::class)
            ->set('nome', '')
            ->call('salvar')
            ->assertHasErrors(['nome' => 'required']);
    }
}

Para depuração, integre com Laravel Telescope ou use o log:

public function updated($property, $value)
{
    logger("Propriedade $property atualizada para $value");
}

Erros comuns:
- "Method not found" — verifique se o método existe na classe e se a diretiva wire:click está correta
- "Property not reactive" — certifique-se de que a propriedade é public e não está sendo serializada incorretamente

Considerações Finais e Comparação com Ferramentas Vizinhas

Quando usar Livewire:
- Projetos Laravel com equipe predominantemente PHP
- Formulários complexos com validação em tempo real
- Painéis administrativos e CRUDs
- Quando o time não tem expertise em JavaScript moderno

Quando considerar alternativas:
- Inertia.js — melhor para SPAs completos que exigem roteamento no frontend
- Vue/React puro — ideal para interfaces altamente interativas e complexas
- Alpine.js — complementar ao Livewire para interações leves no frontend

Limitações do Livewire:
- Dependência de conexão HTTP (cada interação gera uma requisição)
- Complexidade em componentes com muitas interações simultâneas
- Consumo maior de banda comparado a SPAs otimizados

Livewire representa uma mudança de paradigma: você obtém interatividade moderna mantendo o conforto e a produtividade do ecossistema Laravel. Para muitos projetos, especialmente aqueles focados em produtividade e manutenibilidade, Livewire é a escolha certa.

Referências