Git Product home page Git Product logo

redux.net's Introduction

⚠️ This repository is no longer maintained ⚠️

Redux.NET

Redux.NET is an attempt to bring Redux concepts to .NET application development. Redux.NET is a PCL project, so it can be used with Xamarin (IOS, Android, Forms), Windows Universal applications, Windows 8.1 applications, WPF, etc...

Build status Nuget

Table of Contents

Motivation

After working on several MVVM applications (Silverlight, WPF, WinRT), I always feel uneasy when they start to get larger :

  • Two-way bindings lead to cascading updates, making it very difficult to predict what would change as the result of a single user interaction.
  • Asynchronous operations make even more laborious to keep the flow of the application in mind.

Dan Abramov, a brilliant javascript developer, faced the same kind of problems with complex web application. His solution? Redux!

The whole state of your app is stored in an object tree inside a single Store. The only way to change the state tree is to emit an Action, an object describing what happened. To specify how the actions transform the state tree, you write pure Reducers.

Installation

You can grab the latest Redux.NET Nuget package or from the NuGet package manager console :

Install-Package Redux.NET

Quick-start

Actions

Actions are payloads of information that send data from your application to your store. They only need to implement the markup interface Redux.IAction.

public class IncrementAction : IAction { }
    
public class DecrementAction : IAction { }
    
public class AddTodoAction : IAction
{
    public string Text { get; set; }
}

Reducers

A reducer is a pure function with ((TState)state, (IAction)action) => (TState)state signature. It describes how an action transforms the state into the next state.

The shape of the state is up to you: it can be a primitive, an array or an object. The only important part is that you should not mutate the state object, but return a new object if the state changes.

namespace Redux.Counter.Universal
{
    public static class CounterReducer
    {
        public static int Execute(int state, IAction action)
        {
            if(action is IncrementAction)
            {
                return state + 1;
            }

            if(action is DecrementAction)
            {
                return state - 1;
            }

            return state;
        }
    }
}

Store

The Store<TState> is the class that bring actions and reducer together. The store has the following responsibilities:

  • Holds application state of type TState.
  • Allows state to be updated via Dispatch(IAction action).
  • Registers listeners via Subscribe(IObserver observer). The Store<TState> class implements IObservable so ReactiveExtensions is a usefull tool to observe state changes.

It’s important to note that you’ll only have a single store in a Redux application. In the examples, I keep it as a static property on the application class.

The Store constructor take an initial state, of type TState, and a reducer.

using Redux;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Redux.Counter.Universal
{
    public sealed partial class App : Application
    {
        public static IStore<int> CounterStore { get; private set; }

        public App()
        {
            InitializeComponent();
            
            CounterStore = new Store<int>(reducer:CounterReducer.Execute, initialState:0);
        }
    
        [...]
    }
}

The following code show how to subscribe to a store and to dispatch actions.

using Redux;
using System;
using Windows.UI.Xaml.Controls;
using System.Reactive.Linq;

namespace Redux.Counter.Universal
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            App.CounterStore.Subscribe(counter => CounterRun.Text = counter.ToString());
        }

        private void IncrementButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            App.CounterStore.Dispatch(new IncrementAction());
        }

        [...]
    }
}

Performance Optimization

Let's take a closer look to a slightly modified version of the todos app example :

    public sealed partial class MainSection : UserControl
    {
        public MainSection()
        {
            this.InitializeComponent();

            App.Store
                .Subscribe(state => TodosItemsControl.ItemsSource = GetFilteredTodos(state));
        }
        
        public static IEnumerable<Todo> GetFilteredTodos(ApplicationState state)
        {
            if (state.Filter == TodosFilter.Completed)
            {
                return state.Todos.Where(x => x.IsCompleted);
            }

            if (state.Filter == TodosFilter.InProgress)
            {
                return state.Todos.Where(x => !x.IsCompleted);
            }

            return state.Todos;
        }
    }

In this example, GetFilteredTodos is calculated every times the application state changed. If the state tree is large, or the calculation expensive, repeating the calculation on every update may cause performance problems. ReactiveExtensions can help us to avoid unnecessary recalculations.

DistinctUntilChanged to the rescue

We would like to execute GetFilteredTodos only when the value state.Filter or state.Todos changes, but not when changes occur in other (unrelated) parts of the state tree. Rx provide an extension method on IObservable DistinctUntilChanged that surfaces values only if they are different from the previous value based on a key selector (or IEqualityComparer). We just need to call DistinctUntilChanged with the appropriate keySelector just before subscribing to the store. Anonymous Types are a handy tool to create selectors since two instances of the same anonymous type are equal only if all their properties are equal. (See Remarks section : Anonymous Types)

To compute GetFilteredTodos only when necessary, we need to modify the previous code like this :

    App.Store
        .DistinctUntilChanged(state => new { state.Todos, state.Filter }) 
        .Subscribe(state => TodosItemsControl.ItemsSource = GetFilteredTodos(state));

Using DevTools

The development tools contain a time machine debugger inspired by Elm debugger and Redux DevTools.

You can get the dev tools package via nuget or via the Nuget package manager console :

Install-Package Redux.NET.DevTools

To use the time machine, just replace the Store with a TimeMachineStore and the application Frame with a DevFrame:

using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Redux.DevTools.Universal;

namespace Redux.Counter.Universal
{
    public sealed partial class App : Application
    {
        public static IStore<int> CounterStore { get; private set; }

        public App()
        {
            InitializeComponent();
            
            CounterStore = new TimeMachineStore<int>(CounterReducer.Execute, 0);
        }

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            if (rootFrame == null)
            {
                rootFrame = new DevFrame
                {
                    TimeMachineStore = (IStore<TimeMachineState>)CounterStore
                };
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                rootFrame.Navigate(typeof(MainPage), e.Arguments);
            }

            Window.Current.Activate();
        }
    }
}

Examples

  • Counter (sources)
    • Windows universal app : Redux.Counter.sln
  • Todo app (sources)
    • Windows universal app : Redux.TodoMvc.Universal.sln
    • Xamarin Andriod : Redux.TodoMvc.Android.sln
    • Xamarin Forms (IOS + Android) : Redux.TodoMvc.Forms.sln
  • Async http (sources)
    • Windows universal app : Redux.Async.sln

License

MIT

redux.net's People

Contributors

guillaumesalles avatar m-zuber avatar rid00z avatar touchesir avatar vinodsantharam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux.net's Issues

What's the status of this project?

Hi all,

I'm really interested in this project. I'm playing around with using react and redux in C# and, particularly after reading the V3.0 discussion, I think this project fits nicely with the way I'd like to work with redux. The ideas coming from @GuillaumeSalles, @cmeeren, @lilasquared and @dcolthorp are great. It's noticeable that in my research on redux in C# that I've come across many blog posts and comments from you guys.

So I'd potentially be keen to use redux.NET and to get involved and contribute, but I'm a bit concerned at seeing pull requests outstanding since October 2016. I'm not criticising, I get it, it's open source and we all have busy lives. I'm just trying to get a feel for what the status is.

Kind regards,

Stephen

Make compatible with .NET Standard

Please make this installable in .NET Standard class libraries.

I've just come across Redux in general and this package in particular, and I'm pumped and ready to get started, but I'm afraid lacking .NET Standard support is a deal breaker. 😢

[Question] Any plans for state persistence?

Having used Redux on React Native I was pleased to see this library as I start to explore Xamarin.

On React Native I used the redux-persist module for managing automatic persistence and rehydration of state in my app.

Are there any plans for something similar with this library, and if not what are people doing for persisting their state when using this in Xamarin apps?

Thanks

Is the return value from Store.Dispatch useful for anything?

Originally titled Middleware - no difference between `return next(action);` or `next(action); return null;`?. See second comment below for new info.

I came across this while implementing a middleware which sent the action off to async "listeners", see the next comment for details.

For simplicity's sake, consider the following middleware:

public static class SomeMiddleware
{
    public static Func<Dispatcher, Dispatcher> CreateMiddleware(IStore<RootState> store)
    {
        return next => action =>
        {
            return next(action);
        };
    }
}

The app seems to run exactly the same if I replace return next(action); with next(action); return null;:

public static class SomeMiddleware
{
    public static Func<Dispatcher, Dispatcher> CreateMiddleware(IStore<RootState> store)
    {
        return next => action =>
        {
            next(action);
            return null;
        };
    }
}

What is the significance of the return value from the middleware? How is it used in Redux.NET?

Won't compile in Xamarin

I'm using xamarin in vs2017 preview for mac and the pcl 4.5 111 profile and it won't compile using version 2.0.0 of Redux.Net. It tells me it was compiled against PCL 5.0 and isn't compatible. Basically same issue as #41 just a different profile I guess.

WPF Dev Tools don't work

Installing the redux.NET Dev Tools NuGet package as shown in the documentation gets a very old version (from 2015). Attempting to use it results in a MissingMethodException. Looking at the source, it seems that the dev tools have been updated since then, but they haven't been pushed to NuGet. Could we get a NuGet package pushed with the latest version of the redux.Net Dev Tools?

[Question] Could this be used as an equivalent to CQRS & Event Sourcing concepts?

Huge Redux fan on the client-side, and recently have been reading up on Event Sourcing in combination with the entire CQRS concept on the .NET side.

That entire concept seems like lots of leg-work, and seems like a grittier version of Redux. I'm not sure if you're familiar with them, but couldn't we use Redux here instead of all that?

It seems like CQRS (command / query / responsibility / separation) is just the general philosophy of Redux itself, to separate and compartmentalize every action.

While the event sourcing, or having a History of the State of your application, is the entire Store concept inside Redux as well.

Just wondering what your thoughts are!
@GuillaumeSalles Great work on this by the way, I hadn't even thought about bringing Redux to C# until the other day 👍

Some people talking about this concept here as well (reduxjs/redux#351)

What is the point of this Redux.NET line

What is the point of this line? I.e., what is the point of calling _stateSubject.OnNext(_lastState); at the end of the Store constructor? My understanding is that it calls any subscriber callbacks (like firing an event). However, I assume it's not possible for anyone to subscribe to the Store before the constructor has finished, so I can't see how it's doing anything.

Saga

Hi,
It will be great if we can have such a Redux-Saga implementation.

Question about Dispatching Actions.

Hi,

I am working on Xamarin application and have been using MVVM so far. I am now trying to incorporate Redux to manage the state.

My question is, which a good place to call Store.Dispatch()? Should I keep it in code behind of Xaml or viewmodel?

It works either ways, but I want to understand which is a better practice? Should I continue using ViewModels after adopting Redux or just wire the state directly to View?

Thanks

Combine Reducer

Is there an ability to support combined Reducers? I have looked over the code and can't see this.

It would be a great feature!

Cannot add to basic Xamarin.Forms project via nuget

New to Xamarin.forms and PCLs, and I'm trying to add via nuget to a Hello world xamarin.forms project when I get the following error:

Could not install package 'Redux.NET 1.0.1'. You are trying to install this package into a project that targets '.NETPortable,Version=v4.5,Profile=Profile259', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

What framework do I need to target - and should I try to update the xamarin.forms starter project to that framework?

The only dependency I see in the .nuspec file is on System.Reactive 3.0.0 which I added already.

Action properties and grouping question

In my project, I am using an action for each individual property within my application state. This is working just fine but I keep coming back to the question, "Should I be grouping my state properties within a single action?". For example, I have a login screen. Should I have one action that is the SetLoginStateAction that contains things such as whether the email is valid, the password, etc...? Or should I have a EmailValidAction, PasswordValidAction and so on? From a technical perspective, I understand that either one should work. I'm more looking for structure preferences.

The call is ambiguous between the following methods or properties: 'System.ObservableExtensions.Subscribe<T>(System.IObservable<T>, System.Action<T>)' and 'System.ObservableExtensions.Subscribe<T>(System.IObservable<T>, System.Action<T>)'

The error in Windows Form, Netframework 4.7.2

The call is ambiguous between the following methods or properties: 'System.ObservableExtensions.Subscribe<T>(System.IObservable<T>, System.Action<T>)' and 'System.ObservableExtensions.Subscribe<T>(System.IObservable<T>, System.Action<T>)'

Nuget package is not up to date

Hi,

The source code of the Store contains the operation "StartWith" in the construction of the observable.
But after decompiling the source code of the nuget package, it seems that the package is not up to date (StartWith operation is missing).

Vinod

Reducer Performance

In a larger app where we have lots of reducers and dispatched actions, does it make sense to try and avoid type checking within each reducer?

For example, the following is 1 reducer in the TodoMvc example:

public static ImmutableArray<Todo> TodosReducer(ImmutableArray<Todo> previousState, IAction action)
{
    if (action is AddTodoAction) {...}
    if (action is ClearCompletedTodosAction) {...}
    if (action is CompleteAllTodosAction) {...}
    ...
    return previousState;
}

When an action gets dispatched in a very large app, we would have to type check against every if-statement within every reducer.

Would it be better to add an ActionType enum to the IAction interface? Then the reducer becomes much simpler with a switch statement to check against the ActionType of the IAction instance:

public static LoginState Reduce(LoginState previousState, IAction action)
{
    switch (action.ActionType)
    {
        case ActionType.LoginStarted:
            return new LoginState() { IsProcessingLogin = true };
        case ActionType.LoginSuccess:
            return new LoginState() { IsProcessingLogin = false };
        case ActionType.LoginFailed:
            return new LoginState() { IsProcessingLogin = false };
        default:
            return previousState;
    }
}

Assuming the TodosReducer example did follow a more performance if-elseif-elseif-else flow vs having an if-if-if-if flow it still would be a matter of whether type checking is more performant than checking against an enum, and I would guess the latter is better.

Could not link assemblies

I've added the Native Xamarin MacOS project in my Xamarin Forms project which was setup on VS for Windows with .Net Standard 2.0 and linked it. It works fine on other platforms. I'm getting following error:

error MM2001: Could not link assemblies. Reason: Error processing method: 'System.Void Redux.Store`1::.ctor(Redux.Reducer`1<TState>,TState,Redux.Middleware`1<TState>[])' in assembly: 'Redux.dll'

While in Debug I get exception:

Xamarin the type initializer for threw an exception

New ActionDispatched event would duplicate StateChanged

I'm working on a quick PR to add an ActionDispatched event to IStore and Store. I figured this was orthogonal to everything else on the store, and the intention was for this event to work as a backing for an extension method providing an IObservable of actions, as has been mentioned a few times (e.g. #47 (comment)).

While making this, it occurred to me that this event would be raised at exactly the same time as StateChanged. The only and critical difference is that ActionDispatched would pass on the action that was received (which is what allows us to build an IObservable out of it).

@GuillaumeSalles, @dcolthorp, @lilasquared, how do you think we should solve this? In order to be able to make an observable of actions that reach the reducer (not actions that are stopped by middleware), we either need to make this an event on the base Store class, or make a subclass of Store which overrides InnerDispatch (which needs to be made virtual as per PR #60).

I'd like the former, because then we could use an extension method to observe the actions, and wouldn't need a subclass (which could be troublesome if we want to implement other stuff that needs subclasses but are orthogonal to this). For example, the ObservableActionStore in PR #62 could be dropped entirely.

Observable collection that keeps suitable items

When an ItemsControl derived control is bound to a collection it creates visuals to display such items.
When you switch reference in ItemsSource to another one all that visuals are thrown away and new visuals are created.
So this is not the best solution for performance reasons.
Hence in order to make this scenario performant required an implementation of INotifyCollectionChanged which can check which items were deleted, which changed, which added and fires appropriate events.

Some questions

Hi, great project. I have a few questions.

When the application state gets big, does a single change in the application cause all GUI to rerender? If so, isn't that a bit of a waste? And if not, how does the app know which specific parts to update?

Also, with lists of items, if we have an array of items and we add an item to the array, would a list box showing those items have to completely rerender? It would also lose things such as internal state as to which item was selected, right?

Create store bug

While I was looking at the thread safety test I came across a bug. If you add the below it will fail.

public void Should_Dispatch_Once_On_Store_Creation() {
    var sut = new Store<int>((state, action) => state + 1, 0);
    var mockObserver = new MockObserver<int>();

    sut.Subscribe(mockObserver);

    CollectionAssert.AreEqual(new[] { 1 }, mockObserver.Values);
}

It could be how it was intended to be designed but redux is implemented with an initial action that is dispatched to call all reducers once when the store is created. below is a similar test for redux and when added to the test suite it fails.

  it('dispatches once on store creation', () => {
    const store = createStore((state, action) => state + 1, 0)
    expect(store.getState()).toEqual(0)
  })

The result being that the current state is 1 and not 0 because of the initial dispatch.

interesting discussion about this in the redux issues

Future of Redux.NET / version 3.0

I open this issue to know what kind of features people would want in a v3.

I don't currently use Redux.NET in my day to day job so I can't really build something useful without feedback.

What improvements I currently have in mind :

  • Remove dependencies on IObservable. I find observable useful to build selectors, to improve performance or execute subscribtion callback on a specific thread thanks to ObserveOn, I think it bring too much confusion for people who don't know Rx.
  • Useful way to extends store like middleware or enhancer without drawbacks. See #48
  • Remove the IAction interface
  • Basic helper to merge ItemsSource. See #46
  • CombineReducer? Not sure about this one since it will be build with reflexion and can introduce error at runtime.
  • Samples

cc @cmeeren, @lilasquared, @dcolthorp :

You definitely put a lot of time to think about Redux or even use it in production. I think we can benefit from our different experiences to shape a new version of Redux that we will help everyone. Your feedback is more than welcome and I'm looking for some people that want to become core contributors of Redux.NET since I don't always have the time to work on it anymore.

Make InnerDispatch protected virtual

Since Store is a low-level API, we should be able to override its members. I've made PR #61 for this.

I came across the need for overriding InnerDispatch when trying to subclass Store to create a kind of ObservableActionStore that surfaces a property IObservable<IAction> Actions. This should only be fired (OnNext called) when the action reaches the inner dispatch, but to override InnerDispatch it needs to be protected virtual.

Edit, see #63 for a better solution to this paragraph. In addition, we might consider implementing IObservable Actions { get; } in the base store. As far as I can see, the functionality would be orthogonal to everything we currently have. With the possible exception of the return value of Dispatch, but maybe an observable of actions would be a more robust solution to whatever needs are currently being served by this return value?

@dcolthorp might have some input here.

Thread safety?

Hi!

Awesome library! Is it thread safe? It seems to me, but I am not that familiar with System.Reactive, so there might be a catch, but better safe then sorry.
Related question: Is there some kind of guarantee on which thread will the subscribed callback be called? For UI work, the main thread would be ideal, but one might dispatch actions in a background thread...
It would be awesome if these things would be clearly documented.
Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.