mrpmorris / Fluxor

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

State Factory. Can it be done or does it go against the whole idea of Flux? #359

Closed alexandrutatarciuc closed 1 year ago

alexandrutatarciuc commented 1 year ago

I have a specific use case where on runtime I generate multiple components in a for loop (later referred to as component X). I need to have a separate state for each of the generated components, however, they'd share the same state record, but would differ by their ID. Intuitively, I think that OnInitialized I should use some kind of factory method to create a state with the ID of component X, if it exists then return the state, if not – create a new one.

rmvermeulen commented 1 year ago

I think you'd just use a list/dictionary. AFAIK it's not possible (currently) to use a feature-state record more than once.

mrpmorris commented 1 year ago

You can have your states as an ImmutableDictionary<TKey, TComponentState>, then you can inject IStateSelection<FeatureState, ComponentState> and select the substate you are interested in.

alexandrutatarciuc commented 1 year ago

@mrpmorris @rmvermeulen Thank you for your answers. This is definitely a viable solution for this use case. However, what happens when I want to dispatch an action for my substate? Say I have a ListState and ItemState. My ListState contains a Name property and an ImmutableList<ItemState> Items, whereas ItemState contains an Id and ItemName property. When a component is interested in the ItemState, it selects the substate it needs using IStateSelection<ListState, ItemState>, the code will look similar to this State.Select(x => x.Items.First(i => i.Id == ItemId)). Now State.Value returns the ItemState I am interested in. But if I want to change the ItemName, I cannot call an ItemSetNameAction, because the reducer will not be able to figure it out. What I have to do is have a ListSetItemNameAction with the ItemId Dispatcher.Dispatch(new ListSetItemNameAction(ItemId, "newName")) and the ListReducer will need to implement something in this fashion

    public static ListState OnSetName(ListState state, ListSetNameAction action)
    {
        var oldItem = state.Items.First(x => x.Id == action.ItemId);
        var newItem = oldStep with
        {
            Name = action.Name
        };
        return state with
        {
            Items = state.Items.Replace(oldItem, newItem)
        };
    }

This is not convenient at all. The ListReducer is suddenly responsible for reducing an action 100% related to ItemState. How do I deal with this? Any input would be appreciated.

mrpmorris commented 1 year ago
[Reducer]
public static MyState Reduce(MyState state, ListSetNameAction action) =>
  state.Items.Select(x => x.Id != action.Id ? x : x with { Name = action.Name }).ToImmutable();

An ImmutableDictionary would be much faster.

alexandrutatarciuc commented 1 year ago

@mrpmorris Cheers for the quick response and solution. It's still inconvenient that for every substep action out there you have to specify the ID of the substep, let alone the fact that this break the single responsibility principle.

P.S. Thanks for the ImmutableDictionary advice, rn I have to use the ImmutabilityList because I am interested in the order of the items, but I'll modify the DTOs so that the Items contain an Position property

mrpmorris commented 1 year ago

It is still a single responsibility, and if you don't know the Id you want to modify there is no way to know what to change.

It's no different to having a list of people.