Skip to content

skclusive/Skclusive.Mobx.Observable

Repository files navigation

Skclusive.Mobx.Observable

Port of MobX for the C# language.

Supercharge the state-management in your Blazor apps with Transparent Functional Reactive Programming (TFRP)

Introduction

MobX is a state-management library that makes it simple to connect the reactive data of your application with the UI. This wiring is completely automatic and feels very natural. As the application-developer, you focus purely on what reactive-data needs to be consumed in the UI (and elsewhere) without worrying about keeping the two in sync.

It's not really magic but it does have some smarts around what is being consumed (observables) and where (reactions), and automatically tracks it for you. When the observables change, all reactions are re-run. What's interesting is that these reactions can be anything from a simple console log, a network call to re-rendering the UI.

MobX has been a very effective library for the JavaScript apps and this port to the C# language aims to bring the same levels of productivity.

Core Concepts

At the heart of MobX are three important concepts: Observables, Actions and Reactions.

Observables

Observables represent the reactive-state of your application. They can be simple scalars to complex object trees. By defining the state of the application as a tree of observables, you can expose a reactive-state-tree that the UI (or other observers in the app) consume.

A simple reactive-counter is represented by the following observable:

using Skclusive.Mobx.Observable;

var counter = ObservableValue<int>.From(0);

More complex observables, such as classes, can be created as well.

using Skclusive.Mobx.Observable;

public class Counter
{
    private readonly IObservableValue<int> _count = ObservableValue<int>.From(0);

    public int Count
    {
        get => _count.Value;
        set => _count.Value = value;
    }

    public void Increment()
    {
        _count.Value++;
    }
}

Computed Observables

What can be derived, should be derived. Automatically.

The state of your application consists of core-state and derived-state. The core-state is state inherent to the domain you are dealing with. For example, if you have a Contact entity, the FirstName and LastName form the core-state of Contact. However, FullName is derived-state, obtained by combining FirstName and LastName.

Such derived state, that depends on core-state or other derived-state is called a Computed Observable. It is automatically kept in sync when its underlying observables change.

State in MobX = Core-State + Derived-State

using Skclusive.Mobx.Observable;

public class Contact
{
    private readonly IObservableValue<string> _firstName;

    private readonly IObservableValue<string> _lastName;

    private readonly IComputedValue<string> _fullName;

    public Contact()
    {
        _firstName = ObservableValue<string>.From();

        _lastName = ObservableValue<string>.From();

        _fullName = ComputedValue<string>.From(() => $"{FirstName}, {LastName}");
    }

    public string FirstName
    {
        get => _firstName.Value;
        set => _firstName.Value = value;
    }

    public string LastName
    {
        get => _lastName.Value;
        set => _lastName.Value = value;
    }

    public string FullName => _fullName.Value;
}

In the example above FullName is automatically kept in sync if either FirstName and/or LastName changes.

Actions

Actions are how you mutate the observables. Rather than mutating them directly, actions add a semantic meaning to the mutations. For example, instead of just doing Value++, firing an Increment() action carries more meaning. Besides, actions also batch up all the notifications and ensure the changes are notified only after they complete. Thus the observers are notified only upon the atomic completion of the action.

Note that actions can also be nested, in which case the notifications go out when the top-most action has completed.

var counter = ObservableValue<int>.From(0);

var increment = Actions.CreateAction<int, int>("Increment", (amount) =>
{
    counter.Value += amount * 2;

    counter.Value -= amount; // oops

    return counter.Value;
});

increment(2);

Reactions

Reactions complete the MobX triad of observables, actions and reactions. They are the observers of the reactive-system and get notified whenever an observable they track is changed. Reactions come in few flavors as listed below. All of them return a IReactionDisposable, a disposable that can be called to dispose the reaction.

One striking feature of reactions is that they automatically track all the observables without any explicit wiring. The act of reading an observable within a reaction is enough to track it!

The code you write with MobX appears to be literally ceremony-free!

IReactionDisposable Reactions.Autorun(Action<IReactionPublic> action)

Runs the reaction immediately and also on any change in the observables used inside action.

using Skclusive.Mobx.Observable;

var greeting = ObservableValue<string>.From("Hello World");

var disposable = Reactions.Autorun((_) =>
{
    System.Console.WriteLine(greeting.Value);
});

greeting.Value = "Hello Mobx";

// done with Autorun
disposable.Dispose();

// Prints:
// Hello World
// Hello MobX

IReactionDisposable Reactions.Reaction<T>(Func<IReactionPublic, T> expression, Action<T, IReactionPublic> effect)

Monitors the observables used inside the predicate() function and runs the effect() when the predicate returns a different value. Only the observables inside predicate() are tracked.

using Skclusive.Mobx.Observable;

var greeting = ObservableValue<string>.From("Hello World");

var disposable = Reactions.Reaction<string>((reaction) => greeting.Value, (value, reaction) =>
{
    System.Console.WriteLine(greeting.Value);
});

greeting.Value = "Hello Mobx";  // Cause a change

// done with reaction()
disposable.Dispose();

// Prints:
// Hello MobX

Installation

Add a reference to the library from NuGet

Credits

This is an attempt to port mobx to dotnet-standard C# libaray.

License

Skclusive.Mobx.Observable is licensed under MIT license