Best Practices
Follow these guidelines to write clean, maintainable state management code with EasyAppDev.Blazor.Store.
✅ Do's - Follow These Patterns
Use Records for State
Always define state as C# records to leverage immutability and structural equality.
public record AppState(int Count, string Name);
Use 'with' Expressions
Always use with expressions to create modified copies of state.
public AppState Increment() => this with { Count = Count + 1 };
Use Immutable Collections
Use ImmutableList and ImmutableDictionary for collections in your state.
public record State(ImmutableList<Item> Items);
Keep State Methods Pure
State methods should be pure functions with no side effects, I/O, or logging.
public State AddItem(Item item) => this with { Items = Items.Add(item) };
Batch Updates When Possible
Chain multiple state transformations into a single update to minimize re-renders.
await UpdateAsync(s => s.SetLoading(true).ClearErrors().ResetForm());
❌ Don'ts - Avoid These Anti-Patterns
Don't Mutate State
Never mutate state properties directly. This breaks immutability and reactivity.
public void Increment() { Count++; } // Wrong! Mutates state
public AppState Increment() => this with { Count = Count + 1 };
Don't Use Mutable Collections
Avoid List<T>, Dictionary<K,V>, and other mutable collections.
public record State(List<Item> Items); // Wrong! Mutable collection
public record State(ImmutableList<Item> Items);
Don't Add Side Effects to State Methods
State methods must be pure functions. Put side effects (logging, API calls) in components.
public State AddItem(Item item)
{
_logger.Log("Adding"); // Wrong! Side effect!
return this with { Items = Items.Add(item) };
}
@code {
async Task AddItem(Item item)
{
Logger.LogInformation("Adding item");
await UpdateAsync(s => s.AddItem(item));
}
}
🔧 Troubleshooting
Common issues and their solutions:
Make sure your component inherits from StoreComponent<TState>.
@inherits StoreComponent<CounterState>
Always use with expressions to create new state instances.
// ✅ Correct
return state with { Count = 5 };
// ❌ Wrong
state.Count = 5;
return state;
Use ImmutableList and its transformation methods like Add(), Remove(), RemoveAll().
// ✅ Correct
return this with { Items = Items.Add(newItem) };
// ❌ Wrong
Items.Add(newItem);
return this;
Use UpdateAsync instead of Update for async state methods.
// ✅ Correct
await UpdateAsync(async s => await s.LoadDataAsync());
// ❌ Wrong
await UpdateAsync(async s => await s.LoadDataAsync());
Make sure you registered the store utilities in Program.cs.
// ✅ Use this registration method
builder.Services.AddStoreWithUtilities(...);
Register utility services before using async helpers.
// ✅ All-in-one registration
builder.Services.AddStoreWithUtilities(...);
// OR manually
builder.Services.AddStoreUtilities();
⚠️ Common Pitfalls
Always use this with { Property = value } instead of direct assignment.
For async state methods, use UpdateAsync(async s => await s.Method()).
Use ImmutableList.Add() not List.Add().
Keep state methods pure. No logging, no API calls, no database access.
While optional, providing action names helps with debugging: Update(s => s.Method(), "ACTION_NAME").
Always call AddStoreUtilities() or use AddStoreWithUtilities() before using StoreComponent<T>.
Each state type needs AddAsyncActionExecutor<TState>() to use ExecuteAsync. Or use AddStoreWithUtilities().
💡 Additional Tips
Use SelectorStoreComponent for granular subscriptions to prevent unnecessary re-renders.
@inherits SelectorStoreComponent<AppState>
@code {
// Only re-render when Counter changes
protected override object SelectState(AppState state) => state.Counter;
}
Use computed properties in your state records for derived values.
public record TodoState(ImmutableList<Todo> Todos)
{
public int CompletedCount => Todos.Count(t => t.Completed);
public double CompletionRate => Todos.Count > 0
? (double)CompletedCount / Todos.Count * 100 : 0;
}
Enable Redux DevTools integration for time-travel debugging and state inspection.
builder.Services.AddStoreWithUtilities(
new AppState(),
(store, sp) => store.WithDefaults(sp, "MyApp")); // DevTools included!