Pular para o conteúdo principal

Entendendo os Eventos de Formulário no Blazor onchange e oninput

Quando eu tenho um modelo no EntityFrameworkCore que tem os atributos int CountryId e Country Country, e no EditForm eu coloco um @onchange no InputSelect nenhum evento é gerado. Mas, se eu colocar um @oninput o evento funciona. Qual o motivo?

Esse é um comportamento esperado e se deve à forma como o Blazor lida com a sincronização de dados e os eventos dos elementos InputSelect.

O Motivo

O evento @onchange em um componente InputSelect do Blazor é projetado para ser disparado quando o valor do elemento é alterado e o controle perde o foco (um evento de "blur"). Isso é o que tipicamente acontece quando um usuário seleciona uma opção e clica fora do select ou navega para outro campo.

No entanto, o evento @oninput é disparado imediatamente a cada alteração no valor do campo. No caso do InputSelect, isso significa que o evento @oninput será disparado assim que o usuário selecionar uma nova opção.

Exemplo Prático

Vamos imaginar o seguinte cenário com o seu EditForm:

  1. O usuário clica no InputSelect.

  2. O usuário seleciona um novo Country.

  3. Imediatamente após a seleção, o Blazor dispara o evento @oninput. A propriedade CountryId do seu modelo é atualizada. Se você tem uma lógica para recarregar dados baseados na CountryId (por exemplo, preencher um InputSelect de cidades), isso vai acontecer neste momento.

  4. O usuário clica fora do InputSelect.

  5. Neste momento, o evento @onchange seria disparado. Mas como o valor já foi atualizado pelo @oninput e não houve mais nenhuma alteração, a "mudança" já foi processada.

Como o @oninput já fez a atualização do modelo e o Blazor já processou a renderização, o @onchange acaba não sendo "necessário" para o propósito de atualizar a sua UI, por isso, parece que ele não é gerado.

Qual usar?

  • Use @onchange quando você precisa que a ação só aconteça após o usuário confirmar a seleção e sair do campo. Isso pode ser útil para validações mais pesadas ou lógicas de negócio que não precisam ser executadas a cada interação.

  • Use @oninput (o mais comum para InputSelect) quando você precisa que a UI seja atualizada dinamicamente assim que a seleção for feita, como no seu caso, onde a mudança de Country deve afetar imediatamente outros campos do formulário.

Em resumo, o @oninput é a forma mais direta de reagir às seleções de um InputSelect no Blazor, e é por isso que ele funciona para o seu cenário. O @onchange tem um comportamento mais "tradicional" de HTML, disparando o evento apenas no "blur".

Mas, no meu caso, quando eu clico fora, fazendo o blur, e deixando o componente verde, o @oninput funciona. Se eu faço o mesmo no @onchange nada acontece, nenhuma das vezes. No DotNet 9 esse é o comportamento padrão?

Esse comportamento que você está descrevendo é uma peculiaridade do Blazor e não se restringe apenas ao .NET 9 — ele tem sido assim em versões anteriores também. A questão principal é a interação entre o data binding bidirecional (@bind-Value) e o evento @onchange.

O que está acontecendo

  1. @bind-Value é um "atalho": Quando você usa @bind-Value="MeuModelo.CountryId", o Blazor faz muito trabalho nos bastidores para você. Ele não apenas define o valor inicial do InputSelect, mas também configura uma lógica interna para atualizar a propriedade MeuModelo.CountryId sempre que o valor do componente de entrada muda.

  2. Uso do @onchange interno: A maneira padrão do @bind-Value de detectar mudanças em um InputSelect é através do evento @onchange. Acontece que o Blazor não permite que você tenha dois manipuladores para o mesmo evento. Como o @bind-Value já está usando o @onchange internamente para a sincronização, seu próprio manipulador @onchange é ignorado. É como se houvesse uma fila, e a sua chamada para o evento ficasse de fora.

  3. @oninput não é o mesmo evento: O evento @oninput é um evento de browser diferente do @onchange. Ele não faz parte do pacote @bind-Value. Por isso, o Blazor permite que você o utilize livremente. Quando você usa @oninput, ele é disparado e executa sua lógica, e o @bind-Value ainda consegue fazer o trabalho dele em paralelo, sem conflito.

Como o @oninput resolve o problema no seu caso?

Quando você seleciona uma nova opção no InputSelect, o navegador dispara o evento oninput. O Blazor escuta isso e executa o código que você associou a ele. Por sorte, o Blazor é rápido o suficiente para que essa execução e a re-renderização aconteçam quase que instantaneamente.

Quando você clica fora, o navegador dispara o evento onchange. Como o valor já foi processado e sincronizado pelo @oninput anteriormente, e o @bind-Value já usou esse valor, não há nada de "novo" para o seu manipulador @onchange fazer, mesmo que ele fosse disparado. O comportamento padrão do @onchange é realmente disparar somente quando o elemento perde o foco.

Soluções para usar o @onchange

Se você realmente precisa usar o @onchange por alguma razão (por exemplo, para ter a lógica de negócio separada da sincronização imediata), existem algumas abordagens:

  • Abandone o @bind-Value: Você pode remover o @bind-Value e gerenciar a sincronização manualmente. Neste caso, o seu @onchange será disparado, e você terá que atualizar a propriedade do seu modelo dentro do seu método.

    Razor CSHTML
    <InputSelect @onchange="HandleCountryChange" Value="@CountryId">
        ...
    </InputSelect>
    
    @code {
        public int CountryId { get; set; }
    
        private void HandleCountryChange(ChangeEventArgs e)
        {
            if (int.TryParse(e.Value.ToString(), out var newCountryId))
            {
                CountryId = newCountryId;
                // Sua lógica customizada aqui
            }
        }
    }
    
  • Use o EditContext.OnFieldChanged: Se você estiver dentro de um EditForm, pode se inscrever no evento OnFieldChanged do EditContext para reagir a qualquer mudança em qualquer campo do formulário. Essa é uma abordagem mais poderosa e flexível para lidar com eventos de forma centralizada.

Em resumo, a razão pela qual seu @onchange não funciona diretamente no InputSelect com @bind-Value é porque o próprio @bind-Value já está usando esse evento. O @oninput funciona porque é um evento separado e permite que você reaja à mudança de valor imediatamente.

E, se eu usar @bind-Value tem alguma possibilidade de disparar um OnCountryChange sem usar o @onchange?

Sim, existe uma forma de disparar uma lógica customizada em resposta a uma mudança de valor usando @bind-Value, mas sem usar o evento @onchange diretamente no componente.

A melhor maneira de fazer isso é utilizando o @bind-Value:after. Essa é uma sintaxe especial do Blazor que permite que você execute um método logo após a propriedade ligada ser atualizada.

Como usar o @bind-Value:after

A ideia é que, quando o valor do seu InputSelect muda (disparado pelo @onchange interno do @bind-Value), o Blazor primeiro atualiza a propriedade do seu modelo (CountryId, no seu caso) e, depois, executa o método que você definiu com @bind-Value:after.

Veja um exemplo de como seria o seu código:

Razor CSHTML
<InputSelect @bind-Value:after="OnCountryChange" @bind-Value="MeuModelo.CountryId">
    @* Suas opções de países aqui *@
</InputSelect>

@code {
    public CountryModel MeuModelo { get; set; } = new CountryModel();

    private void OnCountryChange()
    {
        // Esta lógica será executada imediatamente após MeuModelo.CountryId ser atualizado.
        Console.WriteLine($"O novo CountryId é: {MeuModelo.CountryId}");

        // Você pode chamar outros métodos aqui para atualizar a UI ou carregar dados.
        // Exemplo: CarregarCidades(MeuModelo.CountryId);
    }
}

Por que essa abordagem é a melhor?

  • Evita Conflito de Eventos: Você continua usando o @bind-Value para a sincronização automática de dados (a forma recomendada no Blazor), mas o seu método OnCountryChange é chamado de forma separada, sem entrar em conflito com a lógica interna do Blazor.

  • Código Mais Limpo: Não é necessário gerenciar a atualização da propriedade do modelo manualmente. A ligação bidirecional (@bind-Value) faz isso por você.

  • Comportamento Confiável: A lógica é executada apenas depois que a propriedade CountryId é atualizada, garantindo que você sempre trabalhe com o valor mais recente.

Essa é a maneira ideal e mais moderna de lidar com a necessidade de executar uma lógica customizada em resposta a uma mudança em um componente de entrada no Blazor, especialmente quando se usa @bind-Value.

Comentários

Postagens mais visitadas deste blog

UML e comunicação entre Componentes

(conteúdo gerado com I.A.) Antes de abordarmos o conceito de Componentes, vale a pena analisar como um programador iniciante costuma estruturar uma aplicação. Geralmente, a escolha é construir um monólito tradicional, agrupando todas as classes de domínio (como Categoria, Produto, Compra, Venda, Pagamento, etc.) em uma única pasta de Entidades. Da mesma forma, criam-se pacotes globais para Repositórios, Serviços e Controllers. O grande problema dessa abordagem é que ela gera um forte acoplamento entre as classes e as camadas do sistema. À medida que a aplicação cresce, isso desencadeia uma série de dificuldades: Manutenção e modelagem complexas: As responsabilidades se misturam. Rapidamente, o código passa a ter diversos trechos onde as classes assumem funções que não deveriam ter, tornando a evolução do sistema confusa. Escalabilidade ineficiente: Para escalar o backend, é necessário replicar a aplicação inteira e todo o banco de dados. Além de consumir mais recursos, isso traz o ri...

Quando escolher Flask ou Quart no Python

Atualmente , considerando o suporte assíncrono já presente no Flask, a escolha entre Flask e Quart para uma API Server depende de vários fatores técnicos. Vejamos alguns critérios: Fonte:  https://pt.stackoverflow.com/questions/51268/qual-a-diferen%C3%A7a-entre-comunica%C3%A7%C3%A3o-ass%C3%ADncrona-e-s%C3%ADncrona Suporte Assíncrono : Flask : O Flask oferece suporte assíncrono por meio de extensões de terceiros, como  flask[async] . No entanto, seu desempenho assíncrono é inferior aos frameworks nativamente assíncronos. Quart : O Quart é uma reimplantação do Flask baseada no padrão ASGI (Asynchronous Server Gateway Interface). Ele utiliza E/S assíncrona em todo o código e é mais adequado para bases de código predominantemente assíncronas. Desempenho : Flask : Como aplicação WSGI, o Flask usa um único worker para cada ciclo de solicitação/resposta. Quando uma solicitação chega a uma visualização assíncrona, o Flask inicia um loop de eventos em uma thread, executa a função da vi...

Metodo para criar relacionamento entre tabelas sem dar erro cíclico EFCore

Quando estamos modelando o banco de dados no Entity Framework Core (ou em qualquer outra ferramenta) é muito comum criarmos o erro cíclico. Este erro ocorre devido ao banco de dados não conseguir determinar onde o relacionamento termina, causando um erro de loop. Vejamos uma situação real onde foi gerado esse problema: Um Professor leciona em várias Turmas, Um Professor leciona uma ou mais Disciplinas, Um Professor leciona determinadas Disciplinas em determinadas Turmas, Uma Turma tem muitos Professores, Em todas as Turmas tem 9 Disciplinas. Em todas as turmas podem ter até 9 Professores. Só por essa descrição já é possível perceber que os relacionamentos podem ser complexos de entender. E tem-se também o problema da navegação em via dupla: Professor-Turmas, Turma-Professores. Então, como modelar essas tabelas sem que o banco entre em loop? Para resolver esse problema no Entity Framework Core e evitar o erro de "Multiple Cascade Paths" (caminhos múltiplos de cascata) comum n...