blazor-state-management-in-memory-container

Estado en memoria en Blazor con State Container

  • 4 min

Un State Container es un servicio que conserva datos compartidos y notifica sus cambios a los componentes interesados.

Si guardas un carrito en una variable de ListaProductos.razor, el dato desaparece cuando el componente se retira al navegar. Para conservarlo, necesitamos un objeto con una vida mayor que la página.

Para que los datos sobrevivan a la navegación, debemos sacarlos de los componentes y moverlos a un lugar seguro, un lugar que viva más tiempo que las propias páginas. A esto lo llamamos Gestión de Estado (State Management).

La técnica más sencilla es registrar un contenedor de estado en DI.

Qué contiene un State Container

Un State Container es una clase C# que:

  1. Almacena datos (Propiedades).
  2. Tiene métodos para modificar esos datos.
  3. Dispara un evento cuando los datos cambian para avisar a los componentes.

Se inyecta en los componentes usando el sistema de Inyección de Dependencias (DI) que vimos en el bloque anterior.

Crear la clase contenedora

Vamos a crear un ejemplo clásico: un contador global que se ve en todas las páginas.

public class CounterState
{
    // 1. El dato que queremos guardar
    public int Count { get; private set; }

    // 2. El evento para avisar a los interesados (Observer Pattern)
    public event Action? OnChange;

    // 3. Métodos para modificar el estado
    public void IncrementCount()
    {
        Count++;
        NotifyStateChanged();
    }

    public void ResetCount()
    {
        Count = 0;
        NotifyStateChanged();
    }

    private void NotifyStateChanged() => OnChange?.Invoke();
}
Copied!

Fíjate que el set de Count es privado. Esto es una buena práctica: obligamos a los componentes a usar los métodos IncrementCount o ResetCount, impidiendo que modifiquen el estado “a lo bruto”.

Registrar el servicio

Ahora debemos decidir cuánto tiempo vive este estado.

  • Si debe pertenecer al circuito o a la aplicación cliente, usamos Scoped.
  • Si debe compartirse entre todos los usuarios del servidor, podríamos usar Singleton, pero solo para estado realmente global y seguro frente a concurrencia.

Lo habitual para datos de usuario (Carrito, Preferencias, Contador de sesión) es Scoped.

// Program.cs
builder.Services.AddScoped<CounterState>();
Copied!

En SSR estático, Scoped significa una instancia por petición, así que este patrón no conserva estado entre navegaciones. En Interactive Auto tampoco debes asumir que la instancia del servidor y la del cliente comparten memoria.

Consumir y modificar el estado

Cualquier componente puede inyectar este servicio y llamar a sus métodos.

Componente A (El que modifica):

@inject CounterState State

<button @onclick="State.IncrementCount">
    Incrementar Global
</button>
Copied!

Reaccionar a los cambios

El detalle importante está en el ámbito del servicio. Si el Componente A modifica el valor, el Componente B (que solo muestra el valor) no se enterará automáticamente. Blazor no monitoriza las propiedades de tus servicios para repintar la pantalla.

El componente que quiera mostrar el valor actualizado debe suscribirse al evento OnChange.

Componente B (El que muestra):

@implements IDisposable
@inject CounterState State

<h3>Cuenta actual: @State.Count</h3>

@code {
    protected override void OnInitialized()
    {
        // Nos suscribimos: "Cuando cambie el estado, ejecuta StateHasChanged"
        State.OnChange += OnStateChanged;
    }

    private void OnStateChanged()
    {
        _ = InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        State.OnChange -= OnStateChanged;
    }
}
Copied!

¿Por qué StateHasChanged? Este método fuerza al componente a re-renderizarse. Al pasarlo como delegado al evento OnChange, le estamos diciendo: “Cada vez que el servicio avise de un cambio, repíntame”.

Ventajas del patrón

  1. Desacoplamiento: Los componentes no hablan entre sí (Padre -> Hijo), hablan con el Servicio (Componente -> Servicio <- Componente).
  2. Persistencia: Puedes navegar de /home a /perfil y volver a /home, y el contador seguirá en el número correcto, porque el servicio Scoped no se destruyó.
  3. Lógica Centralizada: Las reglas para modificar los datos están en la clase CounterState, no dispersas por la UI.

Límites del estado en memoria

El estado en memoria tiene un límite: El refresco del navegador (F5). Como el servicio vive en la memoria RAM del servidor (Blazor Server) o del navegador (WASM), si el usuario pulsa F5, la memoria se limpia y el contador vuelve a 0 (o el carrito se vacía).

Para solucionar esto y hacer que los datos sobrevivan incluso si se cierra el navegador, necesitamos persistencia física. No necesitamos una base de datos completa para esto; el navegador nos ofrece LocalStorage.