Stop Using MVVM

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 RelayCommands or DelegateCommands, 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 ICommands, it might be time to rethink your architecture—and give MVUX a try.


🔗 Resources:

10 thoughts on “Stop Using MVVM”

  1. Well, congrats you just reinvented Redux for react to other technologies. Big list of Icommands vs big list of reducers…

    Reply
    • Yes, I totally acknowledge that we’re not inventing the concept. I think you’ll find the implementation quite interesting as it combines the patterns of MVU with the power of data binding in the context of a Windows/Uno XAML based application.

      Reply
  2. While I agree that MVVM is overbloated abstraction, new “(Model-View-Update-eXtended)” doesn’t bring simplicity here – I read your article, which is specially intended to explain benefit of MVUX, but I see just portions of code and zero explanations. Try to give this task (explanation) to more capable people, you did it dilettantish way. I read article and still don’t catch what you wanna say.

    Reply
    • Yes, you’re right, the post was designed to get you to think beyond using MVVM, rather than being a deep dive into how MVUX works. The documentation here provides more information on MVUX and if you have specific questions, leave a comment and I’ll try and answer them

      Reply
  3. This is still just MVVM. Let’s not get ridiculous with the wacky terms. Unidirectional data flow has been a thing for MVVM since the early 2010’s with React/Redux. You don’t need to invent a new acronym for discovering this.

    Reply
    • Yep, I get that at the end of the day it’s still data binding, and buried in the generated code is the INotifyPropertyChanged implementation that’s used to update the UI, arguably making this MVVM. I think in the context of architectural patterns MVVM is typically a bunch of properties (using INotifyPropertyChanged) and perhaps commands – what you’d expect to see if you used the community toolkit mvvm library. Yes, you can then implmenet unidirectional data flow but this becomes problematic if you try it with immutable objects in a typical MVU style – the two approaches don’t blend well and often leads to UI that has weird refreshing issues, particularly when you have lists of items.

      Reply
  4. How does MVUX handles the equivalent of ViewModel nesting?
    I’ve had this a lot in the past where there is a hierarchy/nesting of ViewModels (like a list of items that are ViewModels in order to have commands and properties in them). With an immutable structure, aren’t there performance concerns (or computing waste) when we reload a new version of a big immutable entity structure just for a simple property change (like changing the IsFavorite property on 1 item of a big list property from another record).

    Reply
    • Typically I avoid having nested viewmodels with commands hanging off them. You can use things like ancestor binding to invoke commands on the viewmodel, passing in the item where the command is invoked.
      Yes, there is a concern regarding reloading of large entity structures, and specificallly loading of large collections after an entity has changed. I’ll see if we can post examples of how this can be handled in MVUX

      Reply
    • Yes, just create an Uno Platform application that includes the WinAppSDK target – this is just a WinAppSDK/WinUI3 application, there’s no Uno dependencies there other than MVUX

      Reply

Leave a comment