Skip to content

ActualLab.Fusion vs MobX / Knockout ​

MobX and Knockout are reactive state management libraries that automatically track dependencies and update the UI when observed data changes. Fusion shares this reactive philosophy but operates across the client-server boundary.

The Core Difference ​

MobX/Knockout provide client-side reactivity. You make objects "observable," and the library automatically tracks which views depend on which data. When data changes, affected views re-render. Server communication requires separate code — you build the API layer and data fetching yourself.

Fusion provides the same automatic dependency tracking, but extends it to server-side computed values and propagates invalidations across the network. Shared C# interfaces serve as the API contract — no separate API layer to build.

MobX Approach (JavaScript) ​

javascript
// Store
class TodoStore {
    @observable todos = [];
    @observable filter = "all";

    @computed get filteredTodos() {
        return this.filter === "all"
            ? this.todos
            : this.todos.filter(t => t.completed === (this.filter === "completed"));
    }

    @action addTodo(title) {
        this.todos.push({ title, completed: false });
    }

    // YOU must build this - fetch from your API
    @action async loadTodos() {
        this.todos = await fetch('/api/todos').then(r => r.json());
    }
}

// React component - automatically re-renders when filteredTodos changes
const TodoList = observer(() => {
    const store = useStore();
    return (
        <ul>
            {store.filteredTodos.map(todo => <TodoItem todo={todo} />)}
        </ul>
    );
});

Fusion Approach ​

csharp
// Shared interface - works on both client and server
public interface ITodoService : IComputeService
{
    [ComputeMethod]
    Task<Todo[]> GetFilteredTodos(string filter, CancellationToken ct);
}

// Server implementation
public class TodoService : ITodoService
{
    [ComputeMethod]
    public virtual async Task<Todo[]> GetFilteredTodos(string filter, CancellationToken ct)
    {
        var todos = await GetAllTodos(ct); // Dependency automatically tracked
        return filter switch {
            "completed" => todos.Where(t => t.Completed).ToArray(),
            "active" => todos.Where(t => !t.Completed).ToArray(),
            _ => todos
        };
    }

    [ComputeMethod]
    public virtual async Task<Todo[]> GetAllTodos(CancellationToken ct)
        => await _db.Todos.ToArrayAsync(ct);
}

// Blazor component - automatically re-renders when server data changes
@inherits ComputedStateComponent<Todo[]>
@code {
    [Parameter] public string Filter { get; set; }
    protected override Task<Todo[]> ComputeState(CancellationToken ct)
        => TodoService.GetFilteredTodos(Filter, ct);
}

Where Each Excels ​

ActualLab.Fusion is better at ​

  • No API plumbing — shared C# interfaces work like Protobuf definitions, but in pure C#
  • Reactive model that extends from client to server seamlessly
  • Automatic server synchronization without manual fetching
  • Built-in caching with dependency-based invalidation
  • Multi-client consistency (everyone sees the same data)
  • Works as client-only state management too

MobX / Knockout are better at ​

  • Minimal boilerplate for client-side-only reactivity
  • Fine-grained reactivity within the browser
  • Working with any UI framework (React, Vue, etc.)
  • JavaScript/TypeScript ecosystems
  • Working with any backend (REST, GraphQL, etc.)

The Plumbing Problem ​

With MobX, you must build the API layer yourself:

javascript
// 1. Define your API endpoints (server)
app.get('/api/todos', async (req, res) => {
    const todos = await db.todos.findAll();
    res.json(todos);
});

// 2. Build fetch logic (client)
@action async loadTodos() {
    const response = await fetch('/api/todos');
    this.todos = await response.json();
}

// 3. Handle updates - when does client refresh? You decide.
// Polling? WebSockets? Manual refresh button?

With Fusion, the interface IS the API:

csharp
// Shared interface = API contract
public interface ITodoService : IComputeService
{
    [ComputeMethod]
    Task<Todo[]> GetTodos(CancellationToken ct);
}

// Client calls interface method → gets cached data → receives updates automatically
// No fetch code. No API endpoints. No polling.

Reactive Model Comparison ​

FeatureMobX/KnockoutFusion
Observable values@observable / ko.observable[ComputeMethod]
Computed values@computed / ko.computed[ComputeMethod] calling other methods
Dependency trackingAutomaticAutomatic
ScopeClient-side onlyClient + Server
Network syncYou build itAutomatic
API layerYou build itShared interfaces

When to Use Each ​

Choose MobX/Knockout when: ​

  • Building a JavaScript/TypeScript frontend
  • State is primarily client-side
  • You already have a REST/GraphQL API
  • Team is familiar with these libraries
  • Working with non-.NET backends

Choose Fusion when: ​

  • Building a .NET application (especially Blazor)
  • You want to eliminate API plumbing
  • Server is the source of truth
  • Multiple clients should see consistent state
  • Caching and automatic invalidation matter

The Shared Philosophy ​

Both MobX/Knockout and Fusion embrace transparent reactivity — you don't manually subscribe to changes or wire up updates. You declare what depends on what, and the framework handles the rest.

The difference:

  • MobX/Knockout: Reactivity within the browser, API is your problem
  • Fusion: Reactivity across the entire stack, API is just shared interfaces

Architectural Comparison ​

MobX/Knockout:
┌─────────────────────────────────────┐
│ Browser                             │
│  ┌─────────┐     ┌─────────────┐    │
│  │ Store   │────▶│ View        │    │
│  │ (state) │     │ (auto-sync) │    │
│  └─────────┘     └─────────────┘    │
└─────────────────────────────────────┘
        ▲
        │ YOU build: fetch(), API, refresh logic
        ▼
┌─────────────────────────────────────┐
│ Server + API (you build this)       │
└─────────────────────────────────────┘

Fusion:
┌─────────────────────────────────────┐
│ Browser                             │
│  ┌─────────┐     ┌─────────────┐    │
│  │ Computed│────▶│ View        │    │
│  │ (cached)│     │ (auto-sync) │    │
│  └────┬────┘     └─────────────┘    │
└───────│─────────────────────────────┘
        │ Shared interface = automatic sync
        ▼
┌─────────────────────────────────────┐
│ Server (same interface)             │
└─────────────────────────────────────┘

If you love MobX's reactive model and wish it extended to your server without building API plumbing, Fusion brings that same philosophy to .NET full-stack development.