mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.23k stars 139 forks source link

Suggestion: Add more advanced selectors #344

Closed berhir closed 1 year ago

berhir commented 1 year ago

Coming from Angular and ngrx/store, I am used to work with selectors. I know there is IStateSelection in Fluxor, but it's not really comparable to what exists in ngrx/store.

I created a PoC for memoized selectors for Fluxor. They work similar as selectors in ngrx/store and Redux. The source is here: https://github.com/berhir/Fluxor.Selectors

I think such selectors are very useful and should be part of the Fluxor project. If you are interested, I am willing to create a PR to continue working on it as part of this repo. Selectors could be a separate package or even part of the main Fluxor package. What do you think?

Here is a short summary of Fluxor.Selectors:

I am quoting the ngrx/store documentation for the benefits:

Selectors are pure functions used for obtaining slices of store state. Fluxor.Selectors provide a few helper functions for optimizing this selection. Selectors provide many features when selecting slices of state:

When using the SelectorFactory.CreateSelector and SelectorFactory.CreateFeatureSelector functions Fluxor.Selectors keep track of the latest arguments in which your selector function was invoked. Because selectors are pure functions, the last result can be returned when the arguments match without reinvoking your selector function. This can provide performance benefits, particularly with selectors that perform expensive computation. This practice is known as memoization.

Create Selectors

The easiest way to create selectors is to use the factory functions:

var selectCounterState = SelectorFactory.CreateFeatureSelector<CounterState>();
var selectCount = SelectorFactory.CreateSelector(selectCounterState, state => state.Count);

Selecting Values

There are two ways to use selectors. But before the selectors can be used, we need access to the IStore. Usually we get it via dependency injection.

  1. To return the selector result immediately, call the Select extension method.
    
    // get via dependency injection
    IStore store;

var count = store.Select(selectCount);


2. Create a subscription that notifies when the selected value changes. A `SelectorSubscription` implements `IState<T>` (which implements `IStateChangedNotifier`)  and `INotifyPropertyChanged`.
```csharp
// get via dependency injection
IStore store;

var countSubscription = store.SubscribeSelector(selectCount);

Then the subscription can be used in a Razor component

<p role="status">Count: @countSubscription.Value</p>

or bind it in .NET MAUI or WPF

<Label Text="{Binding countSubscription.Value, StringFormat='Count: {0}'}" />
berhir commented 1 year ago

Ok, I was pointed to #213 where you say that you don't like selectors. And there are also related discussions in #220 and #221. From my POV selectors are essential, but that's just my opinion. I will definitely continue my work and will use selectors in my projects. I was hoping to get more feedback and your expertise when they are part of this repo. But it's no problem for me to keep the code in my repo and to publish my own package.

In any case I would appreciate your feedback on the topic and my implementation.

mrpmorris commented 1 year ago

Selectors encourage people to have state that looks like a DB (all people, all customers, etc) and have views look them up using selectors.

The problem with this approach is that you can never know when any of the individual people/customer items can be removed from the state, the state grows, and the app effectively has a memory leak.

It is better to have different task-based states, where the same customer data can be in more than one of them. Because then you can clear out the task state when you are no longer interested in that task.

The only reason I have IStateSelection is for when someone wants their state to be a dictionary of states - for example "BrowserOpenedTabs".