The Model-View-ViewModel (MVVM) pattern has long been a cornerstone in building maintainable and testable UIs across frameworks like WPF, Xamarin, Uno Platform, and others. It provides a clean separation of concerns and enables powerful data binding. But despite its popularity and maturity, MVVM often introduces unnecessary complexity and friction—particularly when it comes to state management and event-driven UI updates.
Enter MVUX—a modern, state-driven evolution of MVVM inspired by unidirectional data flow architectures like Redux. MVUX simplifies UI development by shifting the focus from object binding and manual state tracking to declarative, immutable state and reactive updates. Let’s explore why developers should consider moving from MVVM to MVUX.
🧭 A Quick Overview of MVVM
MVVM is composed of three main components:
- Model: Represents the domain data or business logic.
- View: The UI layer (XAML, HTML, etc.).
- ViewModel: The intermediary that exposes data and commands for the View.
The View binds to the ViewModel using data binding (e.g., INotifyPropertyChanged
in .NET). Commands are typically exposed as ICommand
implementations for interaction handling.
Benefits of MVVM:
- Clear separation of UI and logic.
- Enables testing without a UI.
- Promotes reuse and abstraction.
But for all its benefits, MVVM has its share of pain points.
🧱 Limitations of MVVM
1. Boilerplate and Verbosity
MVVM often requires a lot of boilerplate: implementing INotifyPropertyChanged
, managing backing fields, writing RelayCommand
s or DelegateCommand
s, etc.
csharpCopyEditprivate string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
This is repeated across most ViewModels, inflating code and reducing developer productivity.
2. State Synchronization is Hard
In MVVM, state is often distributed across multiple properties and can become difficult to track, especially in complex UIs. There’s no single source of truth for UI state.
3. Command Explosion
Every user interaction typically requires a dedicated command. This leads to an explosion of ICommand
implementations or lambdas, often with overlapping responsibilities.
4. Implicit Flow and Debugging Difficulty
MVVM relies on implicit bindings between View and ViewModel. When things go wrong, debugging a broken binding or a misfired command can be a frustrating experience.
🚀 Introducing MVUX: A Better Way
MVUX (Model-View-Update-eXtended) is a modern take on state-driven UI development. It’s designed to reduce boilerplate, centralize state, and embrace unidirectional data flow with immutable updates—leading to simpler, more maintainable, and predictable UIs.
MVUX Core Concepts:
- State: A single immutable object representing the UI state.
- Reducers: Pure functions that return new state based on an input and the current state.
- Effects: Handle asynchronous operations like API calls or side-effects.
- View: Declaratively renders UI based on the current state.
🧪 Side-by-Side Example: Business Search
Application includes a service that exposes a SearchAsync method.
public interface IAbnLookupService
{
ValueTask<IImmutableList<BusinessResult>> SearchAsync(string name, CancellationToken cancellationToken = default);
}
The MainPage of the application includes a TextBox where the user can enter a business name or ABN, and a ListView that displays the search results. Here is the MainViewModel as defined using MVVM (MVVM Toolkit).
public partial class MainViewModel : ObservableObject
{
private readonly IAbnLookupService _abnLookupService;
[ObservableProperty]
private string _searchText = string.Empty;
public ObservableCollection<BusinessResult> SearchResults { get; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanSearch), nameof(SearchButtonText))]
[NotifyCanExecuteChangedFor(nameof(SearchCommand))]
private bool _isSearching = false;
public bool CanSearch => !IsSearching;
public string SearchButtonText => IsSearching ? "Searching..." : "Search";
[RelayCommand]
private async Task HandleEnterKeyAsync()
{
if (CanSearch)
{
await SearchAsync();
}
}
public MainViewModel(IAbnLookupService abnLookupService)
{
_abnLookupService = abnLookupService;
SearchResults = new ObservableCollection<BusinessResult>();
}
[RelayCommand(CanExecute = nameof(CanSearch))]
private async Task SearchAsync()
{
var searchTerm = SearchText?.Trim();
SearchResults.Clear();
if (string.IsNullOrEmpty(searchTerm))
{
return;
}
// Show loading state
IsSearching = true;
try
{
var results = await _abnLookupService.SearchAsync(searchTerm);
SearchResults.AddRange(results);
}
finally
{
// Reset button state
IsSearching = false;
}
}
}
In addition to basic properties for SearchText and SearchResults, the MainViewModel exposes properties IsSearching and SearchResultText that are used to control the appearance of the Search button.

Here’s the equivalent using MVUX.
public partial record MainModel(IAbnLookupService abnLookupService)
{
// State for search text
public IState<string> SearchText => State<string>.Value(this, () => string.Empty);
// Feed for search results
public IListFeed<BusinessResult> SearchResults =>
SearchText
.Where(text => !string.IsNullOrWhiteSpace(text))
.SelectAsync(abnLookupService.SearchAsync)
.AsListFeed();
}
The SearchText property is two way data bound to the search TextBox, and the SearchResults feed is derived from the current SearchText value. As the user types in the TextBox, the SearchResults is automatically executed. There are no additional properties required to indicate that the search is in progress.

Note that MVUX makes use of a FeedView in the UI in order to automatically display a progress indicator when the search is being executed.
✅ Advantages of MVUX
1. Centralized State
Everything is driven from a single state object. You always know where the source of truth lives, making debugging and testing far easier.
2. Less Boilerplate
No property-changed notifications, no commands to wire up—just pure data and transformations.
3. Predictable UI Behavior
With unidirectional data flow, each action leads to a new state, and the view renders from state. This makes behavior easier to reason about and reduces bugs from hidden bindings.
4. Built-in Side Effect Management
MVUX separates side-effects (like network calls or file I/O) from state logic, making code cleaner and more testable.
5. Better Testability
Reducers are pure functions: given the same input, they always return the same output. This makes unit testing trivial.
🧪 When Should You Use MVUX?
MVUX is particularly well-suited for:
- Applications with complex UI state or flows.
- Apps where you want strict control and visibility into state changes.
- Teams looking to reduce code complexity and improve maintainability.
- Developers familiar with Redux or functional paradigms.
🧠 Conclusion
MVVM has served us well, but it shows its age in modern, state-heavy applications. MVUX is a powerful evolution that embraces immutability, functional updates, and unidirectional data flow to make UI development simpler, clearer, and more maintainable.
If you’re tired of verbose ViewModels, hunting down binding errors, or managing a growing list of ICommand
s, it might be time to rethink your architecture—and give MVUX a try.
🔗 Resources: