React to .NETState management

The question arrives late in an evaluation. A team has usually already decided to look at cross-platform .NET for some other reason: a Windows and macOS desktop build that a browser wrapper did not satisfy, an existing .NET backend with the team that maintains it, a support horizon longer than a JavaScript build chain tends to hold still. Then someone opens the project templates, looks for the place the store goes, and does not find one.

There is no Redux or Zustand to install here. The work those libraries do is divided between two patterns that the platform ships with, and the one you get is a setting in the project template: MVVM or MVUX. They resolve the same question in opposite directions, which is why picking between them matters more than the library choices you are used to making. React below means React on the web, with React Native noted where the two diverge, and Uno Platform is the .NET implementation throughout.

01 / The patterns

The two patterns, briefly

MVVM puts a view model between the view and the model. The view model exposes mutable properties, the view binds to them in both directions, and a property change raises a notification that updates the bound elements. In current .NET you write this with the CommunityToolkit.Mvvm source generators, where [ObservableProperty] generates the property and the code that raises its change notification, and [RelayCommand] generates the command (ObservablePropertyAttribute).

MVUX is an implementation of Model, View, Update built to support data binding. Its entities are assumed to be immutable, and record types are the documented way to get that, so an update produces a new instance instead of mutating the old one. The model exposes feeds and states, the analyzers generate a ViewModel for each partial class or record whose name ends in Model, carrying a property for every public feed, and the view binds to that generated ViewModel rather than to the model itself. Operations are asynchronous by default, and a feed carries its own progress, error, and empty conditions alongside its value (MVUX overview).

02 / One screen

Holding state that one screen owns

In React this is useState, or useReducer when the transitions get involved enough to name. The value lives in the component, and setting it re-runs the component so the render output reflects the new value.

Under MVVM the same state is a property on the view model, annotated with [ObservableProperty], which generates the public property and the change notification the bindings listen for. The attribute goes on a field, or on a partial property where the .NET 9 SDK and C# preview are available.

Under MVUX it is an IState on the model, which accepts input and binds in both directions while carrying the same progress and error metadata that feeds carry (State reference). The view does not bind to the model. It binds to the ViewModel the analyzers generate from it, which carries a property for each feed and state the model exposes.

What diverges is the update path. React re-runs the component function and reconciles the result, while XAML data binding notifies the bindings attached to that property and updates the bound elements with no component function to re-run.

03 / Several screens

Sharing state between screens

In React this is Context for values a subtree needs, or a store such as Zustand when Context starts causing renders you did not want. Either way the mechanism is positioned in the component tree: a provider wraps the part of the tree that can see the value.

On the .NET side sharing is a container concern. A service is registered during host configuration and injected into the view models or models that need it, which is the same mechanism under both patterns (Dependency injection overview).

For changes that have to reach a screen which is not asking for them, CommunityToolkit Messenger carries the broadcast, and MVUX documents the pattern for letting create, update, and delete messages flow into a list state (Messaging with MVUX).

A React provider's reach is decided by where it sits in the tree, and a registered service's reach is decided by its lifetime in the container. Moving a screen changes the first and leaves the second alone.

04 / Server data

Holding data the server owns

This is where the React side has moved furthest. TanStack Query holds server data with staleness windows, deduplication, retries, pagination, and background refetch, and the current mainstream position treats remote data as its own category with its own tool rather than as rows in a global store (React State Management in 2025, September 2025).

Under MVUX the boundary is in the type system. An IFeed or IListFeed is read-only data pulled from a service, carrying its loading, error, and none conditions with it. An IState or IListState is what the user edits. IListFeed when the collection is read-only and pulled from a service, IListState when it is edited (Loading and displaying lists).

FeedView renders the feed's status through its own templates and exposes a refresh command from inside the template (FeedView).

Under MVVM there is no equivalent. Remote data arrives through HttpClient, Refit, or a Kiota client generated from an OpenAPI document (HTTP overview), and the progress and failure conditions are properties you declare and maintain yourself.

The gap

Neither .NET pattern documents a query cache. A list state keeps its value, which the documentation itself calls caching, but that is value retention rather than a staleness policy. No page documents request deduplication or a background refetch, and every refresh described in the documentation is one your code or your user asks for. If TanStack Query's cache semantics are the reason your current architecture works, treat that as a gap until someone shows you otherwise.

05 / URL

Keeping state in the URL

In React on the web this is the router, with a library such as nuqs when search parameters need to behave like state. React Native has no address bar, and the equivalent state lives in the navigation state instead, which is one of the places the two React targets answer the same question differently.

On the .NET side this belongs to neither pattern. Uno.Extensions Navigation owns it. Routes come from a registered RouteMap, and a region is a part of the interface that manages navigation, marked with Region.Attached and named with Region.Name (Navigation regions). Data travels to a screen with the Navigation.Data attached property, a result comes back to the calling view model through NavigateBackWithResultAsync, and on WebAssembly deep linking lets the path part of the URI name a location the application navigates to, enabled by default in projects generated from the unoapp template at version 4.9 or later. The same route table serves the native targets, where the URL is absent and the route is not.

A React router works in both directions: the URL drives the view, and navigating rewrites the URL, which is what makes search parameters usable as state. The Uno Platform documentation covers the inbound direction and does not state that navigating inside a running application writes the new route back to the address bar, so read this row as inbound deep links matched and the round trip unconfirmed.

So the mapping is uneven. Of the four categories a React developer sorts state into, only the first two are decided by the MVVM or MVUX choice. Remote data is a pattern concern under MVUX and a library concern under MVVM, and URL state belongs to the navigation stack under both.

06 / The mapping

The mapping, in one table

What you holdReactMVVMMVUX
State one screen ownsuseState, useReducerproperty generated by [ObservableProperty]IState on the model
State several screens shareContext, or a store such as Zustandservice in the container, Messenger for broadcastsame service, feeds on the model, Messaging into a list state
Data the server ownsTanStack Query or SWRHttpClient, Refit, or Kiota, with status tracked by handIFeed and IListFeed with status built in, rendered by FeedView
State in the URLrouter, or nuqs for search paramsUno.Extensions Navigation, WebAssembly deep linkingidentical, the pattern does not change it
07 / Commitment

What the choice commits you to

The pattern is a property of the application rather than of a feature. Enabling MVUX is a single line, MVUX; inside <UnoFeatures>, because MVUX is an Uno Feature (Set up an MVUX project).

The MVVM side works the same way. The Mvvm feature adds support for the CommunityToolkit.Mvvm package, and the package version can be pinned with a CommunityToolkitMvvmVersion property (Using the Uno.Sdk, MVUX FAQ).

The project template offers the choice as a presentation setting with three values, None, MVVM, and MVUX. The blank preset defaults to None, and the recommended preset defaults to MVUX (presentation setting), so no dialog stops to ask the question.

Two caveats attach to that line. The guide for adding MVUX to an existing application assumes the Single Project template, so an older layout carries a migration to Single Project first. Running both patterns in one application is documented and not recommended, and the first requirement in the FAQ is avoiding a ViewModel and a Model with the same name in the same namespace (MVUX FAQ).

The documentation does not price the cost of converting presentation code already written from one pattern to the other, and it does not describe the steps. Treat the switch as cheap to enable and unpriced to complete. If that matters to your evaluation, convert one real screen and time it.

08 / Concession

Where React wins

React's per-category assembly is an advantage when the categories genuinely differ in kind. A team that wants TanStack Query's cache semantics for remote data and nothing at all for the rest gets exactly that, and can replace one piece a year later without touching the others. Each decision is made at the moment the need appears, with the information available then, and the blast radius of getting one wrong is one category.

The .NET side asks for a different commitment. The pattern spans the application, so a team that wants MVUX for a data-heavy dashboard and MVVM for a settings screen is working against the documented recommendation. That is a decision about the whole application, taken early, and revisited by converting code rather than by swapping a dependency.

The recommended preset arrives with MVUX selected, so a team that never opens the presentation step has chosen at dotnet new, before anyone knows which screens will be mostly reading and which will be mostly editing, while React asks the same question four times later with more information each time.

09 / Choosing

Choosing

Start from MVUX when the application is mostly reading, when screens present asynchronous data that can fail or come back empty, when the team already thinks in immutable state and one-way flow, or when you want the loading and error conditions to be part of the type rather than properties you remember to maintain.

Start from MVVM when the application is mostly forms and editing, when the team has XAML experience and existing view model code to bring across, when you are joining a codebase that is already MVVM, or when a library you depend on expects INotifyPropertyChanged.

Both lists are conditions to check against your own project, and both rest on documented mechanics rather than on a measurement.

For an application whose screens present asynchronous data that can fail or come back empty, and whose shared state outlives the screen that created it, I would build on cross-platform .NET, starting from MVUX. Two of the four categories are the reason. Loading, error, and empty are part of the feed's type rather than properties you declare and remember to maintain, and a shared service's reach is its lifetime in the container rather than its position in a tree that a refactor can move. The other two are where the case is weaker, and they are the conditions below.

Three conditions where I would not:

  • If TanStack Query's cache semantics are load-bearing in your current architecture, nothing documented on the .NET side replaces them, and that is the gap described above rather than a preference.
  • If search parameters have to survive a copied URL, the round trip is unconfirmed, and confirming it is work to do before committing rather than after.
  • If the product ships to the web alone, the constraint that brings people to this comparison is not in play, and this recommendation has nothing to say to you.