Docs›Reatividade›Visão geral do MadWire
Reatividade

Visão geral do MadWire

state, action, model, render parcial.

MadWire é o sistema de reatividade server-first do MAD framework — inspirado no Livewire (Laravel), construído sobre Mad\Component\MadComponent + Blade puro. Ele permite construir interfaces ricas sem escrever JavaScript: propriedades públicas da classe viram state, métodos públicos viram actions, e a UI se atualiza automaticamente a cada interação.

Como funciona

Todo MadComponent renderiza um wrapper com o estado atual serializado e criptografado. Cada interação (mad:click, mad:model, mad:change, mad:submit) dispara um POST AJAX para um endpoint dedicado — /app/_mad-wire no admin, /public/_mad-wire em páginas públicas reativas (MadSitePage) — processado por Mad\Component\MadComponentHandler.

1. Browser carrega HTML com <div mad-component="X" mad-state="..." mad-id="...">
2. Usuario interage (mad:click, mad:model, mad:change, mad:submit)
3. mad-livewire.js monta um POST com:
     mad_state  = state criptografado atual (AES-256-GCM)
     mad_id     = identificador do wrapper no DOM
     mad_action = nome do metodo PHP a chamar
     mad_model  = valores coletados de todos os [mad:model] do wrapper
4. MadComponentHandler decripta o state, hidrata a instancia, aplica os
   mad:model recebidos, chama o metodo (resolvendo argumentos por nome/tipo)
5. O diff de estado (snapshot ANTES vs DEPOIS da action) gera ops parciais
   automaticas; a action pode tambem retornar um MadResponse com ops explicitas
6. Resposta JSON { id, html?, ops[], partial? } - JS aplica no DOM sem reload

Hello, MadWire

Um contador clássico já mostra os dois lados do ciclo: estado público e ações.

Controller

use Mad\Component\MadComponent;

class Contador extends MadComponent
{
    protected static string $wrapper = self::INTERNAL;

    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }

    public function decrement(): void
    {
        $this->count = max(0, $this->count - 1);
    }

    public function reset(): void
    {
        $this->count = 0;
    }

    protected function view(): string|array
    {
        return 'admin.contador';
    }
}

View — admin/contador.blade.php

<mad-page-container>
    <mad-page-header title="Contador" icon="hash" />
    <mad-page-content>
        <div style="text-align:center;padding:2rem;">
            <h1 style="font-size:5rem;">{{ $that->count }}</h1>
            <div style="display:flex;gap:.5rem;justify-content:center;">
                <mad-btn mad:click="decrement" variant="outline">-</mad-btn>
                <mad-btn mad:click="reset" variant="ghost">Reset</mad-btn>
                <mad-btn mad:click="increment" variant="primary">+</mad-btn>
            </div>
        </div>
    </mad-page-content>
</mad-page-container>

Pronto. Cada clique em + ou - chama o método PHP correspondente, atualiza $count, e o DOM reflete o novo valor — sem JS manual, sem AJAX explícito, sem state global no cliente.

Conceitos centrais

Conceito Mecanismo Descrição
state props públicas Toda propriedade public do MadComponent vira state. Serializada e criptografada (AES-256-GCM) a cada response.
action método público Métodos públicos podem ser chamados do client via mad:click, mad:submit ou mad:change.
model mad:model Bind bidirecional input ↔ prop pública. Sincroniza no próximo POST (ou imediatamente com .live).
auto-bind retorno void Action que retorna void tem o state comparado antes/depois — o framework infere e emite as ops parciais sozinho.
partial render MadResponse ->html(), ->val(), ->manageRow(), ->bind() atualizam só o seletor alvo — sem re-render total do componente.

Lifecycle

Inspirado no Livewire 3. Na primeira carga (GET): boot() → mount() → rendering() → render() → rendered() → dehydrate(). Em requisições AJAX (mad:click/mad:model): boot() → hydrate() → updating()/updated() → a action → de novo rendering() → render() → rendered() → dehydrate().

class MeuForm extends MadComponent
{
    public function boot(): void
    {
        // Roda em TODA requisicao (primeira carga + cada action AJAX)
        // Bom para setup compartilhado: ler config, resolver tenant, etc.
    }

    public function mount(): void
    {
        // Roda so na primeira carga (GET) - NAO roda de novo em actions
        $this->produtos = Produto::where('ativo', '1')->get();
    }

    // Action - chamada via mad:click="onSalvar"
    public function onSalvar(): MadResponse
    {
        // ...
        return MadToast::success('Salvo!');
    }

    // Hook auto-bind - roda apos cada prop publica mudar via mad:model
    public function updated(string $name, mixed $value): void
    {
        // ex: recalcular total quando "quantidade" muda
    }

    // Hook auto-bind - roda ANTES da prop mudar
    public function updating(string $name, mixed $old, mixed $new): void
    {
        // ex: validar o novo valor antes de aceitar
    }

    protected function view(): string|array
    {
        return 'admin.meu-form';
    }
}
Não guarde segredos em props públicas

Tudo que é prop public entra no state — e o state vai para o client (mesmo cifrado). Senhas, tokens e dados sensíveis devem ficar em props private/protected (resolvidas de novo em boot()/hydrate() a cada request), nunca em props públicas.

Estado serializado e criptografado

O state vai no atributo mad-state do wrapper, serializado em JSON e cifrado com Mad\Http\MadStateCrypt — AES-256-GCM (criptografia autenticada): o conteúdo não é apenas assinado, é ilegível para o client, e qualquer bit-flip é detectado pela tag de autenticação GCM (o decrypt simplesmente falha e o componente é re-hidratado do zero). Por isso o endpoint /app/_mad-wire / /public/_mad-wire não depende de CSRF para validar o state em si — ele já é autenticado criptograficamente — mas o token CSRF (header X-CSRF-TOKEN) ainda é exigido pela rota.

A API JavaScript do MadWire

A engine client vive em packages/mad-framework/assets/mad-livewire.js (publicada em public/lib/mad/mad-livewire.js) e expõe cinco métodos para os casos em que uma diretiva não dá conta — normalmente dentro de um mad-on:*, de um callback de biblioteca externa ou de um MadResponse->script():

MétodoFaz
MadWire.call(idOuEl, action = '', params = [])Dispara uma action como se fosse um mad:click
MadWire.set(idOuEl, prop, valor) / MadWire.set(idOuEl, objeto)Seta prop(s) e re-renderiza — o mesmo que o atalho $set
MadWire.refresh(idOuEl)Reenvia os mad:model atuais e re-renderiza, sem chamar action — o mesmo que $refresh
MadWire.configure(opcoes)Ajusta a config global; hoje só debounceDelay (default 300 ms, usado pelo mad:model.live)
MadWire.initLoadingElements(root)Re-esconde os [data-mad-loading] (e mostra os [data-mad-loading-remove]) dentro de root — necessário só depois de injetar HTML por fora do morph da engine

O primeiro argumento aceita tanto o mad-id do wrapper (string) quanto um elemento qualquer dentro dele — nesse caso a engine sobe a árvore até achar o [mad-component] mais próximo.

MadWire não é window.MadWire

MadWire é declarado como const no escopo do módulo mad-livewire.js — não é publicado em window. Ele funciona em qualquer código carregado depois no mesmo escopo global de script clássico, mas window.MadWire é undefined, e um if (window.MadWire) ... vai silenciosamente não fazer nada. Escreva MadWire.set(...) direto, sem o prefixo, e sem guard em window. (O mesmo não vale para Mad e MadErrorModal, esses sim publicados em window pelos mesmos bundles.)

Quando usar MadWire

Forms reativos Ideal
Cascata de selects, recálculos ao digitar, field-list dinâmica.
Listagens com filtros Ideal
Sidebar de filtros, busca com debounce, paginação reativa.
Dashboards live
Métricas e gráficos que atualizam ao trocar período/filtro.
Wizards multi-step
Steps com validação por etapa e navegação preservando state.
Evite para: páginas 100% estáticas
HTML público sem interação usa Blade puro (uma rota Route::get em routes/web.php, ou uma MadSitePage) — sem o overhead de um componente reativo.
Evite para: integrações externas
Para consumo por outro sistema (app mobile, terceiros), use a REST API (Route::apiResource → ApiResourceController), que devolve JSON puro.

Próximos passos